Files
ThomasandClaude Sonnet 5 1998bf4273 Strip dead Windows-only code paths, add LTO/march build flags, seed default GM account
Removes now-unreachable #ifdef _WIN32 branches across Common/ and the three
server binaries (all builds are Linux-only), deletes the entirely unused
SEH exception-handling shim and an orphaned vendored MySQL client header.
Enables LTO and -march=x86-64-v2 in Release builds for a small free perf win.

Also seeds a default GM account (gmadmin/changeme) via
database sql files/gm_account.sql so a fresh deploy has something to log
into the web admin panel with, switches the MariaDB data directory from a
Docker named volume to a host bind mount for visibility/backup, and expands
the README with a fuller Docker deployment walkthrough.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 15:42:36 +02:00

471 lines
23 KiB
C++

/*
Rose Online Server Emulator
Copyright (C) 2006,2007 OSRose Team http://www.osrose.net
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
depeloped with Main erose/hrose source server + some change from the original eich source
*/
#include "worldserver.h"
// Map Process
PVOID MapProcess( PVOID TS )
{
bool only_npc = false;
while(GServer->ServerOnline)
{
pthread_mutex_lock( &GServer->PlayerMutex );
pthread_mutex_lock( &GServer->MapMutex );
for(UINT i=0;i<GServer->MapList.Map.size();i++)
{
CMap* map = GServer->MapList.Map.at(i);
if( map->PlayerList.size()<1 )
only_npc = true;
else
only_npc = false;
//continue;
if(!only_npc)
{
// Player update //------------------------
for(UINT j=0;j<map->PlayerList.size();j++)
{
CPlayer* player = map->PlayerList.at(j);
if(!player->Session->inGame && player->Session->Respawned == 1) //only set in pakUserDied on receiving respawn packet 0x753
{
Log( MSG_INFO, "ingame: = %i respawned: = %i",player->Session->inGame,player->Session->Respawned);
if(!player->RespawnPlayer(player)) //so we respawn the player
{
Log( MSG_INFO, "Player not respawned correctly");
}
else
{
Log( MSG_INFO, "Player Respawned" );
}
player->Session->inGame = true;
player->Session->Respawned = 2; //testing toggle
}
if(player->IsDead( )) continue;
if(player->UpdateValues( )) //checks if player is on a cart. otherwise returns true
{
//if(player->Session->Respawned == 2){Log( MSG_INFO, "Running UpdatePosition" );}
player->UpdatePosition( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran UpdatePosition" );}
}
if(player->IsOnBattle( ))
{
//if(player->Session->Respawned == 2){Log( MSG_INFO, "Player is in battle so call DoAttack" );}
player->DoAttack( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran DoAttack" );}
}
player->RefreshBuff( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran refreshbuff" );}
player->PlayerHeal( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran playerheal" );}
player->Regeneration( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran regeneration" );}
player->CheckPlayerLevelUP( );
//if(player->Session->Respawned == 2){Log( MSG_INFO, "ran Check Levelup" );}
if(3600000 < (UINT)GServer->round((clock( ) - player->Session->LogTime))) //1 hour since login
{
//if(player->Session->Respawned == 2){Log( MSG_INFO, "updating logtime stuff" );}
player->Session->logtime += 1;
player->Session->TotalLogTime ++;
GServer->DB->QExecute("UPDATE accounts SET logtime = %i, totlogtime = %i WHERE id = %i", player->Session->logtime, player->Session->TotalLogTime, player->Session->userid);
player->Session->LogTime = clock();
}
//if(player->Session->Respawned == 2){Log( MSG_INFO, "Checking autosave" );}
if( GServer->Config.AUTOSAVE == 1 )
{
clock_t etime = clock() - player->lastSaveTime;
if( etime >= GServer->Config.SAVETIME*1000 )
{
player->savedata( );
player->lastSaveTime = clock();
}
//if(player->Session->Respawned == 2){Log( MSG_INFO, "saved player data" );}
}
//if(player->Session->Respawned == 2){Log( MSG_INFO, "Checking ItemXPTimer" );}
if(player->Stats->ItemXPTimer > 0) // does the player have a timed XPRate buff?
{
clock_t etime = clock() - player->Stats->ItemXPTime;
if(etime >= player->Stats->ItemXPTimer) // turn off timed XPRate buff when time exceeds timer
{
player->Stats->ItemXPTime = 0;
player->Stats->ItemXPRate = 1;
}
//if(player->Session->Respawned == 2){Log( MSG_INFO, "set XPRates and stuff" );}
}
player->Session->Respawned = 0; //resetting respawned status
}
// Monster update //------------------------
pthread_mutex_lock( &map->MonsterMutex );
//spawn TD monsters
unsigned nextmon = map->TDMobList[map->TDNextSpawn];
if(nextmon != 0)
{
//Log(MSG_DEBUG,"Spawning TD mobs");
time_t etime = time(NULL) - map->lastTDSpawnTime;
if(etime >= (time_t)(map->TDMobDelay/1000)) // check if elapsed time (seconds) is greater than spawn delay (TDMobDelay is in ms)
{
//spawn the next monster on the list
//Log(MSG_DEBUG,"Spawning TD mob of type %i at position %i in the list for map %i",nextmon, map->TDNextSpawn, map->id);
map->AddMonster(nextmon, GServer->WPList[map->id][1].pos, 0, 0, 0, 1, 0, 999 ); //spawn a TD mob with no AI
//Log(MSG_DEBUG,"Spawned mob in addmonster");
map->TDMobList[map->TDNextSpawn] = 0; //clear the spawn
//Log(MSG_DEBUG,"cleared the spawn");
map->TDNextSpawn++; //move the pointer
//Log(MSG_DEBUG,"moved the pointer");
if(map->TDNextSpawn > 100)map->TDNextSpawn = 0;
map->lastTDSpawnTime = time(NULL);
}
}
//TD spawn end
for(UINT j=0;j<map->MonsterList.size();j++)
{
CMonster* monster = map->MonsterList.at(j);
//UINT thistimer = monster->AItimer;
clock_t etime = clock() - monster->lastAiUpdate;
//if(etime < monster->AItimer) continue; //not time to run AI for this monster yet
if(monster->hitcount == 0xFF)//this is a delay for new monster spawns this might olso fix invisible monsters(if they attack directly on spawning the client dosn't get the attack packet(its not in it's visible list yet))
{
monster->hitcount = 0;
//monster->DoAi(monster->thisnpc->AI, 0);
monster->DoAi(monster->monAI, 0);
monster->lastAiUpdate=clock();
}
//still need this for special spawns
if(!map->IsNight( ) && monster->Status->nightonly)// if day, delete all night time monsters
{
//Log( MSG_INFO, "Night Only monster deleted. Type %i", monster->montype);
map->DeleteMonster( monster, true, j );
continue;
}
if(map->IsNight() && monster->Status->dayonly)
{
//Log( MSG_INFO, "Day Only monster deleted. Type %i", monster->montype);
map->DeleteMonster( monster, true, j );
continue;
}
//Do TD stuff
if(monster->monAI == 999 && etime >= monster->AItimer) //It's a TD mob
{
monster->UpdatePosition( );
float distance = GServer->distance( monster->Position->current, monster->Position->destiny );
//Log( MSG_INFO, "Found a TD monster %f from target. Waypoint type = %i stance %i speed %i", distance,GServer->WPList[map->id][monster->NextWayPoint].WPType,monster->Stats->stance, monster->Stats->Move_Speed);
//Log( MSG_INFO, "monster location X: %f Y: %f",monster->Position->current.x, monster->Position->current.y);
//Log( MSG_INFO, "WP location X: %f Y: %f ", GServer->WPList[map->id][monster->NextWayPoint].pos.x,GServer->WPList[map->id][monster->NextWayPoint].pos.y);
if(distance < 1) //monster has reached it's destination
{
switch(GServer->WPList[map->id][monster->NextWayPoint].WPType)
{
case 1: //Start waypoint
case 2: //regular waypoint. Increment waypoint counter. Set detiny to next waypoint
{
//Log( MSG_INFO, "Monster reached waypoint %i type: %i", monster->NextWayPoint,GServer->WPList[map->id][monster->NextWayPoint].WPType);
monster->NextWayPoint++;
monster->Position->destiny.x = GServer->WPList[map->id][monster->NextWayPoint].pos.x;
monster->Position->destiny.y = GServer->WPList[map->id][monster->NextWayPoint].pos.y;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, monster->clientid );
ADDWORD ( pak, 0x0000 );
ADDWORD ( pak, monster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x * 100 );
ADDFLOAT ( pak, monster->Position->destiny.y * 100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance ); //should be walking
GServer->SendToVisible(&pak, monster);
}
break;
case 3: //Final waypoint. do stuff. suicide
{
//Log( MSG_INFO, "Reached final waypoint");
//monster->Stats->HP = 0;
map->DeleteMonster( monster, true, j );
//Log( MSG_INFO, "monster successfully deleted");
continue;
}
break;
default:
Log( MSG_INFO, "WPType %i not recognized",GServer->WPList[map->id][monster->NextWayPoint].WPType);
break;
}
}
else
{
//send move packet again to try to synchronise the monster movement
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, monster->clientid );
ADDWORD ( pak, 0x0000 );
ADDWORD ( pak, monster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x * 100 );
ADDFLOAT ( pak, monster->Position->destiny.y * 100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance ); //should be walking
GServer->SendToVisible(&pak, monster);
}
monster->lastAiUpdate = clock();
}
//end TD stuff
if(!monster->PlayerInRange( )) continue;
//if(!monster->UpdateValues( )) continue;
monster->UpdatePosition( );
if(monster->IsOnBattle( ))
{
monster->DoAttack( ); // why was this commented? Monsters were not attacking
//monster->DoAi(monster->thisnpc->AI, 2); //don't use thisnpc->AI any more.
if(etime >= monster->AItimer)
{
monster->DoAi(monster->monAI, 2);
monster->lastAiUpdate = clock();
}
//Log(MSG_INFO,"Monster type: %i current HP: %i",monster->montype, monster->Stats->HP);
}
else
{
//monster->DoAi(monster->thisnpc->AI, 1);
if(etime >= monster->AItimer)
{
monster->DoAi(monster->monAI, 1);
monster->lastAiUpdate = clock();
}
}
monster->RefreshBuff( );
if (monster->IsSummon())
{
monster->SummonUpdate(monster,map, j);
continue;
}
if(monster->IsDead())
{
if(clock() - monster->DeathDelayTimer > GServer->Config.DeathDelay)
{
//Log(MSG_DEBUG,"Found dead monster montype %i",monster->montype);
monster->OnDie( ); //all this does is give exp
//Log(MSG_DEBUG,"back from giving exp");
monster->DoAi(monster->monAI, 5);
//Log(MSG_DEBUG,"ran AI");
map->DeleteMonster( monster, true, j );
//Log(MSG_DEBUG,"deleted monster");
continue;
}
/*else
{
Log(MSG_DEBUG,"Dead monster found. waiting for death delay timer");
}*/
}
}
}
if(only_npc)
pthread_mutex_lock( &map->MonsterMutex );
//AIP for NPCs
for(UINT j=0;j<map->NPCList.size();j++)
{
CNPC* npc = map->NPCList.at(j);
if(npc->thisnpc->AI != 0)
{
CAip* script = NULL;
for(unsigned k=0; k < GServer->AipList.size(); k++)
{
if (GServer->AipList.at(k)->AInumber == npc->thisnpc->AI)
{
script = GServer->AipList.at(k);
break;
}
}
if(script == NULL)
{
//Log( MSG_WARNING, "Invalid AI script for AI %i", npc->thisnpc->AI );
continue;
}
UINT thistimer = script->minTime * 1000; //seems to be set in seconds in AIP
if(thistimer<(UINT)GServer->round((clock( ) - npc->lastAiUpdate))) //check AIP conditions when the timer calls for it
{
CNPCData* thisnpc = GServer->GetNPCDataByID( npc->npctype );
if(thisnpc == NULL)
{
Log( MSG_WARNING, "Invalid montype %i", npc->npctype );
continue;
}
CMonster* monster = new (nothrow) CMonster( npc->pos, npc->npctype, map->id, 0, 0 );
monster->thisnpc = thisnpc;
monster->CharType = 4;
int AIP = monster->thisnpc->AI;
int lma_previous_eventID = npc->thisnpc->eventid;
monster->DoAi(monster->thisnpc->AI, 1);
//check if eventID changed, if we do it in AIP conditions / actions, it just fails...
if (lma_previous_eventID != npc->thisnpc->eventid)
{
//Log(MSG_WARNING,"Event ID not the same NPC %i from %i to %i in map %i, npc->thisnpc->eventid=%i !", npc->npctype, lma_previous_eventID, monster->thisnpc->eventid, map->id, npc->thisnpc->eventid);
npc->thisnpc->eventid = monster->thisnpc->eventid;
BEGINPACKET( pak, 0x790 );
ADDWORD ( pak, npc->clientid );
ADDWORD ( pak, npc->thisnpc->eventid );
GServer->SendToMap(&pak, map->id);
}
if(AIP == GServer->Config.AIWatch)
Log(MSG_DEBUG,"NPC AI number %i successfully run",monster->thisnpc->AI);
if(monster->IsOnBattle())
{
//Log(MSG_DEBUG,"NPC %i is registered as in battle",monster->thisnpc->id);
monster->DoAttack( );
}
GServer->ClearClientID( monster->clientid ); // temp actor for running the NPC's AIP script; never added to MonsterList
delete monster;
npc->lastAiUpdate = clock();
if(AIP == GServer->Config.AIWatch)
Log(MSG_DEBUG,"LastAiUpdate clock reset");
}
}
//LMA: Sometimes another NPC does the job for you.
if(npc->thisnpc->eventid != GServer->ObjVar[npc->npctype][0])
{
int new_event_id = GServer->ObjVar[npc->npctype][0];
//Log(MSG_WARNING,"(2)Event ID not the same NPC %i from %i to %i in map %i, npc->thisnpc->eventid=%i !",npc->npctype,npc->thisnpc->eventid,new_event_id,map->id,npc->thisnpc->eventid);
npc->thisnpc->eventid = new_event_id;
//LMA: We have to change the event ID here since we didn't send the clientID :(
BEGINPACKET( pak, 0x790 );
ADDWORD ( pak, npc->clientid );
ADDWORD ( pak, npc->thisnpc->eventid );
GServer->SendToMap(&pak,map->id);
}
}
pthread_mutex_unlock( &map->MonsterMutex );
}
pthread_mutex_unlock( &GServer->MapMutex );
pthread_mutex_unlock( &GServer->PlayerMutex );
usleep(GServer->Config.MapDelay * 1000); // MapDelay is in milliseconds; usleep wants microseconds
}
pthread_exit( NULL );
return 0;
}
// Visibility Process
PVOID VisibilityProcess(PVOID TS)
{
while(GServer->ServerOnline)
{
pthread_mutex_lock( &GServer->PlayerMutex );
pthread_mutex_lock( &GServer->MapMutex );
for(UINT i=0;i<GServer->MapList.Map.size();i++)
{
CMap* map = GServer->MapList.Map.at(i);
map->CleanDrops( );//moved for test
map->RespawnMonster( );//moved for test
if( map->PlayerList.size()<1 )
continue;
for(UINT j=0;j<map->PlayerList.size();j++)
{
CPlayer* player = map->PlayerList.at(j);
if(!player->Session->inGame) continue;
if(!player->VisiblityList()) Log(MSG_WARNING, "Visibility False: %u", player->clientid );
}
}
pthread_mutex_unlock( &GServer->MapMutex );
pthread_mutex_unlock( &GServer->PlayerMutex );
usleep(GServer->Config.VisualDelay * 1000); // VisualDelay is in milliseconds; usleep wants microseconds
}
pthread_exit(NULL);
return 0;
}
// World Process
PVOID WorldProcess( PVOID TS )
{
int adminQueueTicks = 0;
while( GServer->ServerOnline )
{
// Poll roughly every 2 seconds regardless of WorldDelay, instead of
// hitting the DB on every tick.
if( ++adminQueueTicks * GServer->Config.WorldDelay >= 2000 )
{
adminQueueTicks = 0;
GServer->PollAdminQueue( );
}
pthread_mutex_lock( &GServer->MapMutex );
for(UINT i=0;i<GServer->MapList.Map.size();i++)
{
CMap* map = GServer->MapList.Map.at(i);
if( map->PlayerList.size()<1 )
continue;
map->UpdateTime( );
pthread_mutex_lock( &map->DropMutex );
//map->CleanDrops( );
pthread_mutex_unlock( &map->DropMutex );
pthread_mutex_lock( &map->MonsterMutex );
//map->RespawnMonster( );
pthread_mutex_unlock( &map->MonsterMutex );
}
pthread_mutex_unlock( &GServer->MapMutex );
GServer->RefreshFairy( );
usleep(GServer->Config.WorldDelay * 1000); // WorldDelay is in milliseconds; usleep wants microseconds
}
pthread_exit(NULL);
return 0;
}
// Shutdown Server Process
void ShutdownServer(PVOID TS)
{
int minutes = (int)(intptr_t)TS;
sleep(minutes*60); // minutes*60000 was milliseconds; sleep() wants seconds
Log( MSG_INFO, "Saving User Information... " );
GServer->DisconnectAll();
Log( MSG_INFO, "Waiting Process to ShutDown... " );
GServer->ServerOnline = false;
int status,res;
res = pthread_join( GServer->WorldThread[0], (PVOID*)&status );
if(res)
{
Log( MSG_WARNING, "World thread can't be joined" );
}
else
{
Log( MSG_INFO, "World thread closed." );
}
res = pthread_join( GServer->WorldThread[1], (PVOID*)&status );
if(res)
{
Log( MSG_WARNING, "Visibility thread can't be joined" );
}
else
{
Log( MSG_INFO, "Visibility thread closed." );
}
res = pthread_join( GServer->MapThread[0], (PVOID*)&status );
if(res)
{
Log( MSG_WARNING, "Map thread can't be joined" );
}
else
{
Log( MSG_INFO, "Map thread closed." );
}
Log( MSG_INFO, "All Threads Closed." );
GServer->isActive = false;
pthread_exit(NULL);
}