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>
554 lines
21 KiB
C++
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;
|
|
}
|