Files
ROSS/Src/World Server/mapfunctions.cpp
T
ThomasandClaude Sonnet 5 725620e23c Fix respawn/spawn timers racing ahead under multi-threading
POSIX clock() sums CPU time across every thread in the process, unlike
Windows' clock() which tracks wall-clock milliseconds. With several
worker threads running, respawn/spawn/AI timers based on clock() raced
far ahead of real elapsed time. Switch spawn-timer bookkeeping to
time(NULL)/time_t (seconds) throughout the world server, and add a
portable clock() shim in sockets.h (backed by CLOCK_MONOTONIC,
millisecond resolution) for remaining call sites so CLOCKS_PER_SEC==1000
still holds like it did on Windows.

Also: fix MapDelay/VisualDelay/WorldDelay being passed to usleep() (us)
without converting from their configured milliseconds; fix the shutdown
timer likewise; stop leaking the temporary actor used to run an NPC's
AIP script; fix CleanDrops() skipping the element that shifts into
position after an erase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 06:35:05 +02:00

554 lines
21 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 "worldmap.h"
// convert a monster to other
CMonster* CMap::ConverToMonster( CMonster* monster, UINT newmontype, bool heal )
{
CNPCData* thisnpc = GServer->GetNPCDataByID( newmontype );
if(thisnpc == NULL)// invalid type
return NULL;
monster->montype = newmontype;
monster->thisnpc = thisnpc;
monster->monAI = monster->thisnpc->AI;
monster->SetStats( );
monster->Stats->MaxHP = monster->GetMaxHP( );
monster->Stats->HP = monster->Stats->MaxHP;
monster->Stats->MaxMP = monster->GetMaxMP( );
monster->Stats->MP = monster->Stats->MaxMP;
if(heal)
monster->Stats->HP = monster->Stats->MaxHP;
if(monster->owner == 0)
{
monster->MonsterDrop->mobdrop = GServer->GetDropData( monster->thisnpc->dropid );
monster->MonsterDrop->mapdrop = GServer->GetDropData( id );
}
BEGINPACKET( pak, 0x774 );
ADDWORD ( pak, monster->clientid );
ADDWORD ( pak, newmontype );
GServer->SendToVisible( &pak, monster );
//monster->OnSpawn( false );
return monster;
}
// convert a npc to other [not working]
CMonster* CMap::ConverToMonster( CNPC* npc, UINT newmontype )
{
CMonster* monster = AddMonster( newmontype, npc->pos, 0, 0, 1 );
if(monster == NULL) // invalid montype
return NULL;
GServer->ClearClientID( monster->clientid );
monster->clientid = npc->clientid;
for(UINT i=0;i<NPCList.size();i++)
{
if(NPCList.at(i)==npc)
NPCList.erase( NPCList.begin()+i );
}
delete npc;
BEGINPACKET( pak, 0x774 );
ADDWORD ( pak, npc->clientid );
ADDWORD ( pak, newmontype );
GServer->SendToVisible( &pak, monster );
return monster;
}
// Teleport player to this map and this coord.
bool CMap::TeleportPlayer( CPlayer* player, fPoint coord, bool TelePassenger )
{
GServer->MapList.Index[player->Position->Map]->RemovePlayer( player, false );
player->Position->Map = id;
player->Position->current = coord;
player->Position->destiny = coord;
Log(MSG_DEBUG,"(TeleportPlayer) Destiny set to target position X: %f Y: %f.",player->Position->current.x,player->Position->current.y);
player->Session->inGame = false;
player->Position->lastMoveTime = clock();
if(player->Stats->HP < 1)player->Stats->HP = player->Stats->MaxHP * 10 / 100;
if(!allowpat || !TelePassenger)
{
if(!allowpat)
player->Status->Stance = 0x03;
player->Ride->Drive = false;
player->Ride->charid= 0;
player->Ride->Ride = false;
}
AddPlayer( player );
BEGINPACKET( pak, 0x07a8 );
ADDWORD ( pak, player->clientid );
ADDWORD ( pak, player->Position->Map );
ADDFLOAT ( pak, player->Position->current.x * 100 );
ADDFLOAT ( pak, player->Position->current.y * 100 );
ADDWORD ( pak, (player->Status->Stance == 0x04?0x0201:0x0001) );
player->client->SendPacket( &pak );
if( player->Ride->Drive && player->Ride->charid != 0 )
{
CPlayer* otherclient = GServer->GetClientByCID( player->Ride->charid );
if( otherclient != NULL )
{
if(TelePassenger)
{
TeleportPlayer( otherclient, coord );
}
else
{
otherclient->Ride->Drive = false;
otherclient->Ride->charid = 0;
otherclient->Ride->Ride = false;
}
}
}
GServer->pakClearUser( player );
GServer->ClearClientID( player->clientid );
player->RestartPlayerVal( );
return true;
}
bool CMap::pakTeleport( CPlayer* player, unsigned int Map, fPoint coord, bool TelePassenger )
{
GServer->MapList.Index[player->Position->Map]->RemovePlayer( player, false );
player->Position->Map = Map;
player->Position->current = coord;
player->Position->destiny = coord;
Log(MSG_DEBUG,"(pakTeleport) Destiny set to target position X: %f Y: %f.",player->Position->current.x,player->Position->current.y);
player->Session->inGame = false;
player->Position->lastMoveTime = clock();
if(!allowpat || !TelePassenger)
{
if(!allowpat)
player->Status->Stance=0x03;
player->Ride->Drive = false;
player->Ride->charid= 0;
player->Ride->Ride = false;
}
AddPlayer( player );
BEGINPACKET( pak, 0x07a8 );
ADDWORD ( pak, player->clientid );
ADDWORD ( pak, player->Position->Map );
ADDFLOAT ( pak, player->Position->current.x*100 );
ADDFLOAT ( pak, player->Position->current.y*100 );
ADDWORD ( pak, (player->Status->Stance == 0x04?0x0201:0x0001) );
player->client->SendPacket( &pak );
if( player->Ride->Drive && player->Ride->charid!=0 )
{
CPlayer* otherclient = GServer->GetClientByCID( player->Ride->charid );
if( otherclient != NULL )
{
if(TelePassenger)
{
TeleportPlayer( otherclient, coord );
}
else
{
otherclient->Ride->Drive = false;
otherclient->Ride->charid= 0;
otherclient->Ride->Ride = false;
}
}
}
GServer->pakClearUser( player );
GServer->ClearClientID( player->clientid );
player->RestartPlayerVal( );
return true;
}
// return near respawn [NULL if there are no respawn]
CRespawnPoint* CMap::GetNearRespawn( CPlayer* player )
{
CRespawnPoint* respawn = NULL;
float distance = 0xffff;
for(UINT i=0;i<RespawnList.size();i++)
{
float resdist = GServer->distance( player->Position->current, RespawnList.at(i)->dest );
if( resdist < distance )
{
respawn = RespawnList.at(i);
distance = resdist;
}
}
return respawn;
}
// Return "start" respawn
CRespawnPoint* CMap::GetStartRespawn( )
{
for( UINT i = 0; i < RespawnList.size(); i++ )
{
if( RespawnList.at(i)->masterdest )
return RespawnList.at(i);
}
return NULL;
}
// Return first respawn
CRespawnPoint* CMap::GetFirstRespawn( )
{
CRespawnPoint* respawn = NULL;
for(UINT i=0;i<RespawnList.size();i++)
{
return RespawnList.at(i);
}
return respawn;
}
// Search a client by clientid
CPlayer* CMap::GetPlayerInMap( UINT id )
{
for(UINT i=0;i<PlayerList.size();i++)
{
if(PlayerList.at(i)->clientid == id )
return PlayerList.at(i);
}
return NULL;
}
// Search a client by charid
CPlayer* CMap::GetCharIDInMap( UINT id )
{
for(UINT i=0;i<PlayerList.size();i++)
{
if(PlayerList.at(i)->CharInfo->charid == id )
return PlayerList.at(i);
}
return NULL;
}
// Search a monster by clientid
CMonster* CMap::GetMonsterInMap( UINT id )
{
for(UINT i=0;i<MonsterList.size();i++)
{
if(MonsterList.at(i)->clientid == id )
return MonsterList.at(i);
}
return NULL;
}
CDrop* CMap::GetDropInMap( UINT id )
{
for(UINT i=0;i<DropsList.size();i++)
{
if(DropsList.at(i)->clientid == id )
return DropsList.at(i);
}
return NULL;
}
CNPC* CMap::GetNPCInMap( UINT id )
{
for(UINT i=0;i<NPCList.size();i++)
{
if(NPCList.at(i)->clientid == id )
return NPCList.at(i);
}
return NULL;
}
void CMap::UpdateTime( )
{
if(id == 0) return;
UINT etime = (UINT)GServer->round((clock() - LastUpdate)/1000);
if(etime < 10)return;
MapTime += (UINT)floor((double)(etime/10));
int Hourlength = int(0xffffffff / 24);
int Minutelength = int(Hourlength / 60);
if(MapTime == 0xffffffff)
{
MapTime = 0;
Date ++;
}
ZoneTime = int(MapTime / 17895697);
if(dayperiod == 0)
dayperiod = 1;
UINT ctime = MapTime%dayperiod;
if(ctime >= morningtime && ctime < daytime)
{
CurrentTime = MORNING;
}
else
if(ctime >= daytime && ctime < eveningtime)
{
CurrentTime = DAY;
}
else
if(ctime >= eveningtime && ctime < nighttime)
{
CurrentTime = EVENING;
}
else
if(ctime >= nighttime)
{
CurrentTime = NIGHT;
}
hour = int(MapTime / Hourlength);
minute = int((MapTime - (hour * Hourlength)) / Minutelength);
LastUpdate = clock();
}
// Clean Drops
void CMap::CleanDrops( )
{
for(UINT j=0;j<DropsList.size();)
{
CDrop* thisdrop = DropsList.at(j);
if( time(NULL) - thisdrop->droptime >= 50 )
DeleteDrop( thisdrop ); // erases this element, so don't advance j - the next drop just shifted into position j
else
j++;
}
}
// Respawn a Monster
void CMap::RespawnMonster( )
{
if(GServer->Config.SpawnType == 1)
{
/*for(UINT k=0;k<MonsterSpawnList.size( );k++)
{
CSpawnArea* thisspawn = MonsterSpawnList.at(k);
if (thisspawn->respawntime<2)thisspawn->respawntime=2;
time_t rtime = time(NULL) - thisspawn->lastRespawnTime;
if((rtime > thisspawn->respawntime && thisspawn->amon<thisspawn->max)|| thisspawn->amon<thisspawn->min )
{
fPoint position = GServer->RandInPoly( thisspawn->points, thisspawn->pcount );
AddMonster( thisspawn->montype, position, 0, thisspawn->mobdrop, thisspawn->mapdrop, thisspawn->id );
thisspawn->lastRespawnTime = time(NULL);
}
}*/
//PY new spawn code
for(UINT k=0;k<MonsterSpawnList.size( );k++)
{
CSpawnArea* thisspawn = MonsterSpawnList.at(k);
time_t rtime = time(NULL) - thisspawn->lastRespawnTime;
if(thisspawn->limit == 1 && IsNight()) //spawn limit is set to day only
continue;
if(thisspawn->RefVar != 0 && thisspawn->RefVal != 0) //only allow this spawn if the correct ObjVar is set to non zero
{
if(GServer->ObjVar[thisspawn->RefVar][0] == thisspawn->RefVal)
continue;
}
if(thisspawn->limit == 2 && !IsNight()) //spawn limit is set to night only
continue;
switch (thisspawn->type)
{
case 0:
// no spawn. this spawn area is inactive
break;
case 1: //normal spawn
if(rtime > (thisspawn->respawntime) && thisspawn->amon < thisspawn->max)
{
fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius );
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
thisspawn->lastRespawnTime = time(NULL);
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
thisspawn->triggercount = 0;
}
break;
case 2: //normal but fill the spawn
if(rtime > (thisspawn->respawntime) && thisspawn->amon < thisspawn->max)
{
for(int i=1;i<thisspawn->max;i++)
{
fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius );
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
}
thisspawn->lastRespawnTime = time(NULL);
}
break;
case 3: //cascade spawn
if(thisspawn->triggercount > thisspawn->triggeramount && thisspawn->amon < thisspawn->max)
{
fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius );
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
thisspawn->lastRespawnTime = time(NULL);
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
thisspawn->triggercount = 0;
}
break;
case 4: //cascade to max
if(thisspawn->triggercount > thisspawn->triggeramount && thisspawn->amon < thisspawn->max)
{
for(int i=1;i<thisspawn->max;i++)
{
fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius );
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
}
thisspawn->lastRespawnTime = time(NULL);
thisspawn->triggercount = 0;
}
break;
case 5: //sub spawns. needs to trigger from a specific montype
if(rtime > (thisspawn->respawntime) && thisspawn->amon < thisspawn->max)
{
//find if any of the trigger mobs exist
for(UINT j=0;j<MonsterList.size();j++)
{
CMonster* monster = MonsterList.at(j);
//if(monster->montype == thisspawn->triggertype && (thisspawn->spawnkey == 4 || thisspawn->triggercount >= thisspawn->triggeramount))
if(monster->montype == thisspawn->triggertype)
{
//found the right monster. now spawn the servant mob in a circle around it
fPoint position = GServer->RandInCircle(monster->Position->current,5);
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
thisspawn->triggercount = 0;
thisspawn->lastRespawnTime = time(NULL);
}
}
}
break;
case 6:
if(rtime > (thisspawn->respawntime) && thisspawn->amon < thisspawn->max)
{
//find if any of the trigger mobs exist
for(UINT j=0;j<MonsterList.size();j++)
{
CMonster* monster = MonsterList.at(j);
//if(monster->montype == thisspawn->triggertype && (thisspawn->spawnkey == 4 || thisspawn->triggercount >= thisspawn->triggeramount))
if(monster->montype == thisspawn->triggertype)
{
//found the right monster. now spawn the servant mobs around it up to the spawn max
for(int i=thisspawn->amon;i<thisspawn->max;i++)
{
fPoint position = GServer->RandInCircle(monster->Position->current,5);
UINT aggro = thisspawn->aggressive;
AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit );
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters
}
thisspawn->lastRespawnTime = time(NULL);
thisspawn->triggercount = 0;
}
}
}
break;
case 7: //TDEF spawn. Spawns thisspawn->max monsters one by one using triggercount to track them
if(rtime > (thisspawn->respawntime) && thisspawn->triggercount < thisspawn->max)
{
fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius );
AddMonster( thisspawn->montype, position, 0, thisspawn->id, 0, true, 0 );
UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 3); // update the counters
thisspawn->lastRespawnTime = time(NULL);
}
break;
default:
break;
}
}
}
if(GServer->Config.SpawnType == 0)
{
for (UINT j = 0; j < MobGroupList.size(); j++)
{
CMobGroup* thisgroup = MobGroupList.at(j);
time_t rtime = time(NULL) - thisgroup->lastRespawnTime;
if (rtime < thisgroup->respawntime || thisgroup->active >= thisgroup->limit)
continue;
for (UINT k = thisgroup->active; k < thisgroup->limit; k++)
{
CMob *thismob;
if (thisgroup->tacMobs.size() > 0 && thisgroup->basicKills >= thisgroup->tacticalpoints)
{
thismob = thisgroup->tacMobs.at(thisgroup->curTac);
thisgroup->basicKills = 0;
thisgroup->curTac++;
if (thisgroup->curTac >= thisgroup->tacMobs.size()) thisgroup->curTac = 0;
} else {
thismob = thisgroup->basicMobs.at(thisgroup->curBasic);
thisgroup->curBasic++;
if (thisgroup->curBasic >= thisgroup->basicMobs.size()) thisgroup->curBasic = 0;
}
for (UINT i = 0; i < thismob->amount; i++)
{
fPoint position = GServer->RandInCircle( thisgroup->point, thisgroup->range );
AddMonster( thismob->mobId, position, 0, thisgroup->id, 1 );
}
}
}
}
}
//update the trigger counts in cascade spawns
void CMap::UpdateSpawnCounter( UINT montype, UINT spawnid, UINT key )
{
if(key != 0) //kick back if key is undefined
{
for(UINT k=0;k<MonsterSpawnList.size( );k++)
{
CSpawnArea* thisspawn = MonsterSpawnList.at(k);
switch (thisspawn->typeoftrigger)
{
case 0: //no trigger
continue;
break;
case 1: //at any location
//specific monster spawns or dies or is attacked anywhere in the current map
if(thisspawn->triggertype == montype && thisspawn->spawnkey == key)
{
thisspawn->triggercount++;
}
case 2: //at a specific spawn id
//specific monster spawns or dies or is attacked at this specific spawn area
if(thisspawn->triggertype == spawnid && thisspawn->spawnkey == key)
{
thisspawn->triggercount++;
}
break;
case 3: //towerdef
thisspawn->triggercount++;
break;
default:
break;
}
}
}
}
// Return true if is night
bool CMap::IsNight( )
{
if(CurrentTime==NIGHT)
return true;
return false;
}
CCharacter* CMap::GetCharInMap( unsigned int id )
{
CMonster* monster = GetMonsterInMap( id );
//Log(MSG_DEBUG, "getcharinmap %i", id);
if(monster != NULL)
{
//Log(MSG_DEBUG, "Monster found type = %i", monster->montype);
return (CCharacter*) monster;
}
CPlayer* player = GetPlayerInMap( id );
if(player != NULL)
return (CCharacter*) player;
return NULL;
}