/* 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;iMapList.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;jPlayerList.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;jMonsterList.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;jNPCList.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;iMapList.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;jPlayerList.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;iMapList.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); }