/* * npcmanager.cpp by Keith Fulton * * Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * 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 (version 2 of the License) * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include "globals.h" #include "net/npcmessages.h" #include "engine/drmessage.h" #include "util/eventmanager.h" #include "util/serverconsole.h" #include "events.h" #include "psserver.h" #include "cachemanager.h" #include "playergroup.h" #include "util/psdatabase.h" #include "gem.h" #include "combatmanager.h" #include "authentserver.h" #include "entitymanager.h" #include "spawnmanager.h" #include "util/log.h" #include "util/psconst.h" #include "psbehave/psworld.h" #include "bulkobjects/pscharacterloader.h" #include "bulkobjects/psaccountinfo.h" #include "clients.h" // Client, and ClientConnectionSet classes #include "client.h" // Client, and ClientConnectionSet classes #include "util/strutil.h" #include "psserverchar.h" #include "util/psxmlparser.h" #include "util/mathscript.h" #include "bulkobjects/servervitals.h" #include "util/serverconsole.h" #include "util/command.h" #include "npcmanager.h" #include "workmanager.h" class psNPCManagerTick : public psGameEvent { protected: NPCManager *npcmgr; public: psNPCManagerTick(int offsetticks, NPCManager *c); virtual void Trigger(); // Abstract event processing function }; #define NPC_TICK_INTERVAL 500 //msec /** * This class is the relationship of Owner to Pet ( which includes Familiars ). */ class PetOwnerSession : public iDeleteObjectCallback { public: int ownerID; // Character ID of the owner int petID; // Character ID of the pet bool isActive; gemActor *owner; NPCManager *manager; // create time double elapsedTime; csString curDate; PetOwnerSession() { ownerID = 0; petID = 0; manager = NULL; owner = NULL; elapsedTime = 0.0f; isActive = false; }; PetOwnerSession( NPCManager *mgr, gemActor *owner, psCharacter* pet) { manager = mgr; if ( owner ) { this->ownerID = owner->GetCharacterData()->GetCharacterID(); this->owner = owner; this->owner->RegisterCallback( this ); } if ( pet ) { this->petID = pet->GetCharacterID(); } if ( owner ) CPrintf(CON_DEBUG,"Created PetSession ( %s, %d )\n", owner->GetName(), pet ); elapsedTime = 0.0f; // Get pet characterdata if ( pet ) { csString last_login = pet->GetLastLoginTime(); if ( last_login.Length() > 0 ) { curDate = last_login.Truncate( 10 ); // YYYY-MM-DD CPrintf(CON_DEBUG,"Last Logged Date : %s\n", curDate.GetData() ); csString curTime ; pet->GetLastLoginTime().SubString(curTime, 11 ); CPrintf(CON_DEBUG,"Last Logged time : %s\n", curTime.GetData() ); int hours = 0, mins = 0, secs = 0; hours = atoi( curTime.Slice( 0, 2 ) ); //hours mins = atoi( curTime.Slice( 3, 2 ) ); secs = atoi( curTime.Slice( 6, 2 ) ); elapsedTime = ( ( ( ( hours * 60 ) + mins ) * 60 ) + secs ) * 1000; } else { curDate = ""; } } }; virtual ~PetOwnerSession() { if ( owner ) owner->UnregisterCallback( this ); }; // Renable Time tracking for returning players void Reconnect( gemActor *owner ) { if ( owner ) { if ( this->owner ) { owner->UnregisterCallback( this ); } this->owner = owner; this->owner->RegisterCallback( this ); } }; /// Disable time tracking for disconnected clients virtual void DeleteObjectCallback(iDeleteNotificationObject * object) { gemActor *sender = (gemActor *)object; if ( sender && sender == owner ) { if ( owner ) { owner->UnregisterCallback( this ); owner = NULL; } } }; /// Update time in game for this session void UpdateElapsedTime( float elapsed ) { gemActor *pet = NULL; int i = 0; if ( owner && isActive) { this->elapsedTime += elapsed; //Update Session status using new elpasedTime values CheckSession(); bool done = false; size_t numPets = owner->GetClient()->GetNumPets(); // Check to make sure this pet is still summoned for( size_t i = 0; i < numPets; i++ ) { pet = owner->GetClient()->GetPet( i ); if ( pet && pet->GetCharacterData() && pet->GetCharacterData()->GetCharacterID() == petID ) break; else pet = NULL; }; if ( pet ) { int hour = 0, min = 0, sec = 0; sec = (long) ( elapsedTime / 1000 ) % ( 60 ) ; min = (long) ( elapsedTime / ( 1000 * 60 ) ) % ( 60 ); hour = (long) ( elapsedTime / ( 1000 * 60 * 60 ) ) % ( 24 ); csString strLastLogin; strLastLogin.Format("%s %02d:%02d:%02d", this->curDate.GetData(), hour, min, sec); pet->GetCharacterData()->SetLastLoginTime( strLastLogin, false ); if ( !isActive ) // past Session time { psserver->CharacterLoader.SaveCharacterData( pet->GetCharacterData(), pet, true ); CPrintf(CON_NOTIFY,"NPCManager Removing familiar %s from owner %s.\n",pet->GetName(),pet->GetName() ); owner->GetClient()->SetFamiliar( NULL ); EntityManager::GetSingleton().RemoveActor( pet ); psserver->SendSystemInfo( owner->GetClientID(), "You feel your power to maintain your pet wane." ); } } } }; // used to verify the session should still be valid bool CheckSession() { double maxTime = GetMaxPetTime(); csString thisDate; time_t curr=time(0); tm* localtm = localtime(&curr); thisDate.Format("%d-%02d-%02d", localtm->tm_year+1900, localtm->tm_mon+1, localtm->tm_mday); if ( !curDate.Compare( thisDate ) ) { curDate = thisDate; elapsedTime = 0; } if ( elapsedTime >= maxTime ) { CPrintf(CON_NOTIFY,"PetSession marked invalid ( %s, %d )\n", owner->GetName(), petID ); this->isActive = false; return false; } return true; } // Uses a MathScript to calculate the maximum amount of time a Pet can remain in world. double GetMaxPetTime() { static MathScript *maxPetTime; double maxTime = 60 * 5 * 1000; if (!maxPetTime) { // Max Mana Script isn't loaded, so load it maxPetTime = psserver->GetMathScriptEngine()->FindScript("CalculateMaxPetTime"); //CS_ASSERT(maxPetTime != NULL); } if ( maxPetTime ) { MathScriptVar* actorvar = maxPetTime->GetOrCreateVar("Actor"); if ( owner ) { actorvar->SetObject( this->owner->GetCharacterData() ); maxPetTime->Execute(); MathScriptVar* timeValue = maxPetTime->GetVar("MaxTime"); maxTime = timeValue->GetValue(); } } return maxTime; } // The == and < operators for BinaryTree<> MUST be the same criteria! int operator==(PetOwnerSession& other) const { // use the familiar as the familiar can only have one owner return ( petID == other.petID ); }; int operator<(PetOwnerSession& other) const { // use the familiar as the familiar can only have one owner return ( petID < other.petID ); }; }; NPCManager::NPCManager(ClientConnectionSet *pCCS, psDatabase *db, EventManager *evtmgr) { clients = pCCS; database = db; eventmanager = evtmgr; psserver->GetEventManager()->Subscribe(this,MSGTYPE_NPCAUTHENT,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_NPCOMMANDLIST,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_NPC_COMMAND,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_DAMAGE_EVENT,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this,MSGTYPE_PET_COMMAND,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_PET_SKILL,REQUIRE_ANY_CLIENT); PrepareMessage(); psNPCManagerTick *tick = new psNPCManagerTick(NPC_TICK_INTERVAL,this); eventmanager->Push(tick); } NPCManager::~NPCManager() { psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_NPCAUTHENT); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_NPCOMMANDLIST); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_NPC_COMMAND); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DAMAGE_EVENT); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_PET_COMMAND); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_PET_SKILL); delete outbound; } void NPCManager::HandleMessage(MsgEntry *me,Client *client) { switch ( me->GetType() ) { case MSGTYPE_DAMAGE_EVENT: { HandleDamageEvent(me); break; } case MSGTYPE_DEATH_EVENT: { HandleDeathEvent(me); break; } case MSGTYPE_NPCAUTHENT: { HandleAuthentRequest(me); break; } case MSGTYPE_NPCOMMANDLIST: { HandleCommandList(me); break; } case MSGTYPE_PET_COMMAND: { HandlePetCommand(me); break; } case MSGTYPE_PET_SKILL: { HandlePetSkill(me); break; } case MSGTYPE_NPC_COMMAND: { HandleConsoleCommand(me); break; } } } void NPCManager::HandleDamageEvent(MsgEntry *me) { psDamageEvent evt(me); // NPC's need to know they were hit using a Perception if (evt.attacker!=NULL && evt.target->GetNPCPtr()) // if npc damaged QueueDamagePerception(evt.attacker,evt.target->GetNPCPtr(),evt.damage); } void NPCManager::HandleDeathEvent(MsgEntry *me) { Debug1(LOG_SUPERCLIENT, 0,"NPCManager handling Death Event\n"); psDeathEvent evt(me); QueueDeathPerception(evt.deadActor); } void NPCManager::HandleConsoleCommand(MsgEntry *me) { csString buffer; psServerCommandMessage msg(me); printf("Got command: %s\n", msg.command.GetDataSafe() ); size_t i = msg.command.FindFirst(' '); csString word; msg.command.SubString(word,0,i); COMMAND *cmd = find_command(word); if (cmd && cmd->allowRemote) { int ret = execute_line(msg.command, &buffer); if (ret == -1) buffer = "Error executing command on the server."; } else { buffer = cmd ? "That command is not allowed to be executed remotely" : "No command by that name. Please try again."; } printf(buffer); printf("\n"); psServerCommandMessage retn(me->clientnum, buffer); retn.SendMessage(); } void NPCManager::HandleAuthentRequest(MsgEntry *me) { Client* client = clients->FindAny(me->clientnum); if (!client) { Error1("NPC Manager got authentication message from already connected client!"); return; } psNPCAuthenticationMessage msg(me); csString status; status.Format("%s, %u, Received NPC Authentication Message", (const char *) msg.sUser, me->clientnum); psserver->GetLogCSV()->Write(CSV_AUTHENT, status); // CHECK 1: Networking versions match if (!msg.NetVersionOk()) { //psserver->RemovePlayer (me->clientnum, "You are not running the correct version of Planeshift for this server."); Error2("Superclient '%s' is not running the correct version of Planeshift for this server.", (const char *)msg.sUser); return; } // CHECK 2: Is the server ready yet to accept connections if (!psserver->IsReady()) { psDisconnectMessage disconnectMsg(client->GetClientNum(), 0, "Server not ready"); psserver->GetEventManager()->Broadcast(disconnectMsg.msg, NetBase::BC_FINALPACKET); if (psserver->HasBeenReady()) { // Locked Error2("Superclient '%s' authentication request rejected: Server is up but about to shutdown.\n", (const char *)msg.sUser ); } else { // Not ready // psserver->RemovePlayer(me->clientnum,"The server is up but not fully ready to go yet. Please try again in a few minutes."); Error2("Superclient '%s' authentication request rejected: Server not ready.\n", (const char *)msg.sUser ); } return; } // CHECK 3: Is the client is already logged in? msg.sUser.Downcase(); msg.sUser.SetAt(0,toupper(msg.sUser.GetAt(0))); if (clients->Find(msg.sUser)) // username already logged in { // invalid // psserver->RemovePlayer(me->clientnum,"You are already logged on to this server. If you were disconnected, please wait 30 seconds and try again."); psDisconnectMessage disconnectMsg(client->GetClientNum(), 0, "Already logged in."); psserver->GetEventManager()->Broadcast(disconnectMsg.msg, NetBase::BC_FINALPACKET); Error2("User '%s' authentication request rejected: User already logged in.\n", (const char *)msg.sUser); return; } // CHECK 4: Check to see if the login is correct. Error2("Check Superclient Login for: '%s'\n", (const char*)msg.sUser); psAccountInfo *acctinfo=CacheManager::GetSingleton().GetAccountInfoByUsername((const char *)msg.sUser); csString password = csMD5::Encode(msg.sPassword).HexString(); if (acctinfo==NULL || strcmp(acctinfo->password,(const char *)password)) // authentication error { if (acctinfo==NULL) { Notify2(LOG_CONNECTIONS,"User '%s' authentication request rejected (Username not found).\n",(const char *)msg.sUser); } else Notify2(LOG_CONNECTIONS,"User '%s' authentication request rejected (Bad password).\n",(const char *)msg.sUser); delete acctinfo; return; } client->SetPlayerID(0); client->SetSecurityLevel(acctinfo->securitylevel); if (acctinfo->securitylevel!= 99) { // psserver->RemovePlayer(me->clientnum, "Login id does not have superclient security rights."); Error3("User '%s' authentication request rejected (security level was %d).\n", (const char *)msg.sUser, acctinfo->securitylevel ); delete acctinfo; return; } // *********Successful superclient login here!********** time_t curtime = time(NULL); struct tm *loctime; loctime = localtime (&curtime); csString buf(asctime(loctime)); buf.Truncate(buf.Length()-1); Debug3(LOG_SUPERCLIENT,0,"Superclient '%s' connected at %s.\n",(const char *)msg.sUser, (const char *)buf); client->SetName(msg.sUser); client->SetAccountID( acctinfo->accountid ); client->SetSuperClient( true ); char addr[30]; client->GetIPAddress(addr); //TODO: database->UpdateLoginDate(cid,addr); superclients.Push(PublishDestination(me->clientnum, client, 0, 0)); psserver->GetAuthServer()->SendMsgStrings(me->clientnum); SendMapList(client); delete acctinfo; status.Format("%s, %u, Superclient logged in", (const char*) msg.sUser, me->clientnum); psserver->GetLogCSV()->Write(CSV_AUTHENT, status); } void NPCManager::Disconnect(Client *client) { // Deactivate all the NPCs that are managed by this client GEMSupervisor::GetSingleton().StopAllNPCs(client->GetAccountID() ); // Disconnect the superclient for (size_t i=0; i< superclients.Length(); i++) { PublishDestination &pd = superclients[i]; if ((Client *)pd.object == client) { superclients.DeleteIndex(i); Debug1(LOG_SUPERCLIENT, 0,"Deleted superclient from NPCManager.\n"); return; } } CPrintf(CON_DEBUG, "Attempted to delete unknown superclient.\n"); } void NPCManager::SendNPCList(Client *client) { int count = GEMSupervisor::GetSingleton().CountManagedNPCs(client->GetAccountID()); // Note, building the internal message outside the msg ctor is very bad // but I am doing this to avoid sending database result sets to the msg ctor. psNPCListMessage newmsg(client->GetClientNum(),count * 2 * sizeof(uint32_t) + sizeof(uint32_t)); newmsg.msg->Add( (uint32_t)count ); GEMSupervisor::GetSingleton().FillNPCList(newmsg.msg,client->GetAccountID()); if (!newmsg.msg->overrun) { newmsg.SendMessage(); client->SetReady(true); } else { Bug2("Overran message buffer while sending NPC List to client %u.\n",client->GetClientNum()); } } void NPCManager::SendMapList(Client *client) { psWorld *psworld = EntityManager::GetSingleton().GetWorld(); csString regions; psworld->GetAllRegionNames(regions); psMapListMessage list(client->GetClientNum(),regions); list.SendMessage(); } void NPCManager::HandleCommandList(MsgEntry *me) { psNPCCommandsMessage list(me); if (!list.valid) { Debug2(LOG_NET,me->clientnum,"Could not parse psNPCCommandsMessage from client %u.\n",me->clientnum); return; } csTicks begin = csGetTicks(); int count[24] = {0}; int times[24] = {0}; char cmd = list.msg->GetInt8(); while (cmd != list.CMD_TERMINATOR && !list.msg->overrun) { csTicks cmdBegin = csGetTicks(); count[cmd]++; switch(cmd) { case psNPCCommandsMessage::CMD_DRDATA: { // extract the data uint32_t len; void *data = list.msg->GetData(&len); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Error2("Received incomplete CMD_DRDATA from NPC client %u.\n",me->clientnum); break; } psDRMessage drmsg(data,len, CacheManager::GetSingleton().GetMsgStrings(), EntityManager::GetSingleton().GetEngine()); // alternate method of cracking // copy the DR data into an iDataBuffer csRef databuf = csPtr (new csDataBuffer (len)); memcpy(databuf->GetData(), data, len); // find the entity and Set the DR data for it gemObject *actorObj = GEMSupervisor::GetSingleton().FindObject(drmsg.entityid); gemNPC *actor = (gemNPC *) actorObj; if (!actorObj) { Error2("Illegal PS_ID %d from superclient!\n",drmsg.entityid); } else if (!actor->IsAlive()) { Debug2(LOG_SUPERCLIENT, actor->GetPlayerID(),"Ignoring DR data for dead npc %s.\n", actor->GetName()); } else { // Go ahead and update the server version actor->SetDRData(drmsg); // Now multicast to other clients actor->UpdateProxList(); actor->MulticastDRUpdate(); if(drmsg.vel.y < -20 || drmsg.pos.y < -1000) //NPC has fallen down { actor->Kill(NULL); CPrintf(CON_DEBUG, "Received bad DR data from NPC %s, killing NPC.", actor->GetName()); break; } } break; } case psNPCCommandsMessage::CMD_ATTACK: { PS_ID attacker_id = list.msg->GetUInt32(); PS_ID target_id = list.msg->GetUInt32(); Debug3(LOG_SUPERCLIENT,attacker_id,"-->Got attack cmd for entity %u to %u\n",attacker_id, target_id); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,attacker_id,"Received incomplete CMD_ATTACK from NPC client %u.\n",me->clientnum); break; } gemNPC *attacker = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(attacker_id)); if (attacker && attacker->IsAlive()) { gemObject *target = (gemObject *)GEMSupervisor::GetSingleton().FindObject(target_id); if (!target) { attacker->SetTarget(target); if (attacker->GetCharacterData()->GetMode() == PSCHARACTER_MODE_COMBAT) psserver->combatmanager->StopAttack(attacker); if(target_id==0) { Debug2(LOG_SUPERCLIENT, attacker_id,"%s has stopped attacking.\n",attacker->GetName() ); } else // entity may have been removed since this msg was queued Debug2(LOG_SUPERCLIENT, attacker_id, "Couldn't find entity %d to attack them.\n",target_id); } else if(attacker->GetTarget() != target) { attacker->SetTarget(target); if (attacker->GetCharacterData()->GetMode() == PSCHARACTER_MODE_COMBAT) psserver->combatmanager->StopAttack(attacker); if ( !target->GetClient() || !target->GetActorPtr()->GetInvincibility() ) { psserver->combatmanager->AttackSomeone(attacker,target,3); // always "normal" attack by npc for now Debug3(LOG_SUPERCLIENT, attacker_id, "%s is now attacking %s.\n",attacker->GetName(),target->GetName() ); } else { Debug3(LOG_SUPERCLIENT, attacker_id, "%s cannot attack GM [%s].\n",attacker->GetName(),target->GetName() ); } } } break; } case psNPCCommandsMessage::CMD_SPAWN: { PS_ID spawner_id = list.msg->GetUInt32(); PS_ID spawned_id = list.msg->GetUInt32(); Debug3(LOG_SUPERCLIENT,spawner_id, "-->Got spawn cmd for entity %u to %u\n",spawner_id, spawned_id); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,me->clientnum,"Received incomplete CMD_SPAWN from NPC client %u.\n",me->clientnum); break; } gemNPC *spawner = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(spawner_id)); if (spawner) { EntityManager::GetSingleton().CloneNPC(spawner->GetCharacterData()); } else { Error1("NPC Client try to clone non existing npc"); } break; } case psNPCCommandsMessage::CMD_TALK: { PS_ID speaker_id = list.msg->GetUInt32(); const char* text = list.msg->GetStr(); Debug3(LOG_SUPERCLIENT,me->clientnum,"-->Got talk cmd: %s for entity %u\n",text, speaker_id); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,me->clientnum,"Received incomplete CMD_TALK from NPC client %u.\n",me->clientnum); break; } gemNPC *speaker = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(speaker_id)); speaker->Say(text,NULL); break; } case psNPCCommandsMessage::CMD_VISIBILITY: { PS_ID entity_id = list.msg->GetUInt32(); bool status = list.msg->GetBool(); Debug3(LOG_SUPERCLIENT,me->clientnum,"-->Got visibility cmd: %s for entity %u\n",status?"yes":"no", entity_id); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,me->clientnum,"Received incomplete CMD_VISIBILITY from NPC client %u.\n",me->clientnum); break; } gemNPC *entity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); entity->SetVisibility(status); break; } case psNPCCommandsMessage::CMD_PICKUP: { PS_ID entity_id = list.msg->GetUInt32(); PS_ID item_id = list.msg->GetUInt32(); int count = list.msg->GetInt16(); Debug4(LOG_SUPERCLIENT,entity_id,"-->Got pickup cmd: Entity %u to pickup %d of %u\n",entity_id,count,item_id); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,entity_id,"Received incomplete CMD_PICKUP from NPC client %u.\n",me->clientnum); break; } gemNPC *gEntity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); psCharacter* chardata = NULL; if (gEntity) chardata = gEntity->GetCharacterData(); if (!chardata) { Debug1(LOG_SUPERCLIENT,0,"Couldn't find character data.\n"); break; } gemItem *gItem = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(item_id)); psItem *item = NULL; if (gItem) item = gItem->GetItem(); if (!item) { Debug1(LOG_SUPERCLIENT,0,"Couldn't find item data.\n"); break; } // If the entity is in range of the item AND the item may be picked up, and check for dead user if ( gEntity->IsAlive() && (gItem->RangeTo( gEntity ) < RANGE_TO_SELECT) && gItem->IsPickable()) { // TODO: Include support for splitting of a stack // into count items. // Chach values from item, because item might be deleted in MoveToInventory. csString qname = item->GetQuantityName(); if ( chardata && chardata->MoveToInventory(item) ) { EntityManager::GetSingleton().RemoveActor(gItem); // Destroy this } else { // TODO: Handle of pickup of partial stacks. } } break; } case psNPCCommandsMessage::CMD_EQUIP: { PS_ID entity_id = list.msg->GetUInt32(); csString item = list.msg->GetStr(); csString slot = list.msg->GetStr(); int count = list.msg->GetInt16(); Debug6(LOG_SUPERCLIENT,entity_id,"-->Got equip cmd from %u: Entity %u to equip %d %s in %s\n", me->clientnum,entity_id,count,item.GetData(),slot.GetData()); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,entity_id,"Received incomplete CMD_EQUIP from NPC client %u.\n",me->clientnum); break; } gemNPC *gEntity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); psCharacter* chardata = NULL; if (gEntity) chardata = gEntity->GetCharacterData(); if (!chardata) { Debug1(LOG_SUPERCLIENT,entity_id,"Couldn't find character data.\n"); break; } int slotID = CacheManager::GetSingleton().slotName.GetID( slot ); if (slotID == PSCHARACTER_SLOT_NONE) { Debug2(LOG_SUPERCLIENT,entity_id,"Couldn't find slot %s.\n",slot.GetData()); break; } // Create the item psItemStats* baseStats = CacheManager::GetSingleton().GetBasicItemStatsByName(item); if (!baseStats) { Debug2(LOG_SUPERCLIENT,entity_id,"Couldn't find base for item %s.\n",item.GetData()); break; } // Make a perminent new item psItem* newItem = baseStats->InstantiateBasicItem( true ); if (!newItem) { Debug2(LOG_SUPERCLIENT,0,"Couldn't create item %s.\n",item.GetData()); break; } newItem->SetStackCount(count); if (chardata->Inventory().EquipIn(slotID,newItem)) { newItem->Save(true); psserver->GetCharManager()->SendOutEquipmentMessages(gEntity, slotID, newItem, psEquipmentMessage::EQUIP); } else { Error2("Adding new item %s to inventory failed.", item.GetData()); delete newItem; } break; } case psNPCCommandsMessage::CMD_DEQUIP: { PS_ID entity_id = list.msg->GetUInt32(); csString slot = list.msg->GetStr(); Debug3(LOG_SUPERCLIENT,entity_id,"-->Got dequip cmd: Entity %u to dequip from %s\n", entity_id,slot.GetData()); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,entity_id,"Received incomplete CMD_DEQUIP from NPC client %u.\n",me->clientnum); break; } gemNPC *gEntity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); psCharacter* chardata = NULL; if (gEntity) chardata = gEntity->GetCharacterData(); if (!chardata) { Debug1(LOG_SUPERCLIENT,entity_id,"Couldn't find character data.\n"); break; } int slotID = CacheManager::GetSingleton().slotName.GetID( slot ); if (slotID == PSCHARACTER_SLOT_NONE) { Debug2(LOG_SUPERCLIENT,entity_id,"Couldn't find slot %s.\n",slot.GetData()); break; } psItem * oldItem = chardata->Inventory().RemoveEquipment(slotID); if (oldItem) { psserver->GetCharManager()->SendOutEquipmentMessages(gEntity, slotID, oldItem, psEquipmentMessage::DEEQUIP); delete oldItem; } break; } case psNPCCommandsMessage::CMD_DIG: { PS_ID entity_id = list.msg->GetUInt32(); csString resource = list.msg->GetStr(); Debug3(LOG_SUPERCLIENT,entity_id,"-->Got dig cmd: Entity %u to dig for %s\n", entity_id,resource.GetData()); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,entity_id,"Received incomplete CMD_DIG from NPC client %u.\n",me->clientnum); break; } gemNPC *gEntity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); psCharacter* chardata = NULL; if (gEntity) chardata = gEntity->GetCharacterData(); if (!chardata) { Debug1(LOG_SUPERCLIENT,entity_id,"Couldn't find character data.\n"); break; } psserver->GetWorkManager()->HandleProduction(gEntity,"dig",resource); break; } case psNPCCommandsMessage::CMD_DROP: { PS_ID entity_id = list.msg->GetUInt32(); csString slot = list.msg->GetStr(); Debug3(LOG_SUPERCLIENT,entity_id,"-->Got drop cmd: Entity %u to drop from %s\n", entity_id,slot.GetData()); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,entity_id,"Received incomplete CMD_DROP from NPC client %u.\n",me->clientnum); break; } gemNPC *gEntity = dynamic_cast (GEMSupervisor::GetSingleton().FindObject(entity_id)); psCharacter* chardata = NULL; if (gEntity) chardata = gEntity->GetCharacterData(); if (!chardata) { Debug1(LOG_SUPERCLIENT,entity_id,"Couldn't find character data.\n"); break; } int slotID = CacheManager::GetSingleton().slotName.GetID( slot ); if (slotID == PSCHARACTER_SLOT_NONE) { Debug2(LOG_SUPERCLIENT,entity_id,"Couldn't find slot %s.\n",slot.GetData()); break; } psItem * oldItem = chardata->Inventory().RemoveEquipment(slotID); if (oldItem) { psserver->GetCharManager()->SendOutEquipmentMessages(gEntity, slotID, oldItem, psEquipmentMessage::DEEQUIP); //TODO... Drop to ground instead of delete delete oldItem; } break; } case psNPCCommandsMessage::CMD_RESURRECT: { csVector3 where; int playerID = list.msg->GetUInt32(); float rot = list.msg->GetFloat(); where.x = list.msg->GetFloat(); where.y = list.msg->GetFloat(); where.z = list.msg->GetFloat(); csString sector = list.msg->GetStr(); Debug7(LOG_SUPERCLIENT,playerID,"-->Got resurrect cmd: ID %u Rot: %.2f Where: (%.2f,%.2f,%.2f) Sector: %s\n", playerID,rot,where.x,where.y,where.z,sector.GetDataSafe()); // Make sure we haven't run past the end of the buffer if (list.msg->overrun) { Debug2(LOG_SUPERCLIENT,playerID,"Received incomplete CMD_RESURRECT from NPC client %u.\n",me->clientnum); break; } psserver->GetSpawnManager()->Respawn(where,rot,sector,playerID); break; } } times[cmd] += csGetTicks() - cmdBegin; cmd = list.msg->GetInt8(); } begin = csGetTicks() - begin; if(begin > 500) { int total = 0; for(int i = 0;i < 24;i++) total += count[i]; csString status; status.Format("NPCManager::HandleCommandList() took %d time. %d commands. Counts: ", begin, total); for(int i = 0;i < 24;i++) if(times[i] > 100) status.AppendFmt("%d: %d# x%d", i, count[i], times[i]); psserver->GetLogCSV()->Write(CSV_STATUS, status); } if (list.msg->overrun) { Debug2(LOG_NET,me->clientnum,"Received unterminated or unparsable psNPCCommandsMessage from client %u.\n",me->clientnum); } } void NPCManager::AddEntity(gemObject *obj) { for (size_t i=0; i< superclients.Length(); i++) { PublishDestination &pd = superclients[i]; obj->Send(pd.client,false,false); } } void NPCManager::RemoveEntity(MsgEntry *me) { psserver->GetEventManager()->Multicast(me, superclients, 0, PROX_LIST_ANY_RANGE); } void NPCManager::UpdateWorldPositions() { if (superclients.Length()) { GEMSupervisor::GetSingleton().UpdateAllDR(); psAllEntityPosMessage msg; GEMSupervisor::GetSingleton().GetAllEntityPos(msg); msg.Multicast(superclients,-1,PROX_LIST_ANY_RANGE); } } void NPCManager::HandlePetCommand( MsgEntry * me ) { psPETCommandMessage msg( me ); gemNPC *pet = NULL; gemActor *familiar = NULL; psCharacter *chardata = NULL; csString firstName, lastName; csString prevFirstName, prevLastName; csString fullName, prevFullName; int familiarID = 0; Client* owner = clients->FindAny(me->clientnum); if (!owner) { Error2("invalid client object from psPETCommandMessage from client %u.\n",me->clientnum); return; } if ( !msg.valid ) { Debug2(LOG_NET,me->clientnum,"Could not parse psPETCommandMessage from client %u.\n",me->clientnum); return; } WordArray words( msg.options ); if ( msg.target.Length() != 0 ) { size_t numPets = owner->GetNumPets(); for( size_t i = 0; i < numPets; i++ ) { pet = dynamic_cast ( owner->GetPet( i ) ); if ( pet && msg.target.CompareNoCase( pet->GetCharacterData()->GetCharName() ) ) break; else pet = NULL; } } else { pet = dynamic_cast (owner->GetFamiliar()); } if ( ( pet == NULL ) && ( msg.target.Length() != 0 ) ) { psserver->SendSystemInfo( me->clientnum, "You do not have a pet named '%s'.", msg.target.Slice(0, msg.target.Length() - 1 ).GetData() ); return; } switch ( msg.command ) { case psPETCommandMessage::CMD_FOLLOW : //pet = dynamic_cast (owner->GetFamiliar()); if ( pet != NULL ) { QueueOwnerCmdFollowPerception( owner->GetActor(), pet ); } break; case psPETCommandMessage::CMD_STAY : //pet = dynamic_cast (owner->GetFamiliar()); if ( pet != NULL ) { QueueOwnerCmdStayPerception( owner->GetActor(), pet ); } break; case psPETCommandMessage::CMD_DISMISS : if ( pet != NULL && pet->IsValid() ) { PetOwnerSession key, *session = NULL; key.ownerID = 0; key.petID = pet->GetCharacterData()->GetCharacterID(); session = OwnerPetList.Find( &key ); // Check for an existing session if ( !session ) { CPrintf(CON_NOTIFY,"Cannot locate PetSession for owner %s.\n",pet->GetName(),owner->GetName() ); } else { session->isActive = false; } psserver->CharacterLoader.SaveCharacterData( pet->GetCharacterData(), pet, true ); CPrintf(CON_NOTIFY,"NPCManager Removing familiar %s from owner %s.\n",pet->GetName(),owner->GetName() ); owner->SetFamiliar( NULL ); EntityManager::GetSingleton().RemoveActor( pet ); } else { if ( owner->GetCharacterData()->familiar_id ) { psserver->SendSystemInfo(me->clientnum,"Your pet has already returned to the netherworld."); } else { psserver->SendSystemInfo(me->clientnum,"You have no familiar to command."); } return; } break; case psPETCommandMessage::CMD_SUMMON : // Attach to Familiar familiarID = owner->GetCharacterData()->familiar_id; if ( owner->GetFamiliar() ) { psserver->SendSystemInfo(me->clientnum,"Your familiar has already been summoned."); return; } if ( familiarID > 0 ) { PetOwnerSession key, *session = NULL; key.ownerID = 0; key.petID = familiarID; session = OwnerPetList.Find( &key ); psCharacter *petdata = psserver->CharacterLoader.LoadCharacterData( familiarID, false ); // Check for an existing session if ( !session ) { // Create session if one doesn't exist session = CreatePetOwnerSession( owner->GetActor(), petdata ); } if ( !session ) { Error2("Error while creating pet session for Character '%s'\n", owner->GetCharacterData()->GetCharName()); return; } if ( session->owner != owner->GetActor() ) session->Reconnect( owner->GetActor() ); // Check time in game for pet if ( session->CheckSession() ) { session->isActive = true; // re-enable time tracking on pet. EntityManager::GetSingleton().CreateNPC( petdata ); pet = GEMSupervisor::GetSingleton().FindNPCEntity( familiarID ); if (pet == NULL) { Error2("Error while creating Familiar NPC for Character '%s'\n", owner->GetCharacterData()->GetCharName()); psserver->SendSystemError( owner->GetClientNum(), "Could not find Familiar GEM and set its location."); return; // If all we didn't do was load the familiar } if (!pet->IsValid()) { Error2("No valid Familiar NPC for Character '%s'\n", owner->GetCharacterData()->GetCharName()); psserver->SendSystemError( owner->GetClientNum(), "Could not find valid Familiar"); return; // If all we didn't do was load the familiar } EntityManager::GetSingleton().Teleport( pet, owner->GetActor() ); owner->SetFamiliar( pet ); // Send OwnerActionLogon Perception pet->SetOwner( owner->GetActor() ); //psserver->npcmanager->SetNPCOwner( Familiar, actor->GetEntity()->GetID() ); } else { psserver->SendSystemInfo(me->clientnum,"You are to weak call your familiar to you any more today."); } } else { psserver->SendSystemInfo(me->clientnum,"You have no familiar to command."); return; } break; case psPETCommandMessage::CMD_ATTACK : if ( pet != NULL ) { if ( pet->GetTarget() != NULL ) { int stance = PSCHARACTER_STANCE_AGGRESSIVE; if ( words.GetCount() != 0 ) { stance = words.GetInt( 0 ); } QueueOwnerCmdAttackPerception( owner->GetActor(), pet ); } else { psserver->SendSystemInfo(me->clientnum,"Your pet needs a target to attack."); } } break; case psPETCommandMessage::CMD_STOPATTACK : if ( pet != NULL ) { QueueOwnerCmdStopAttackPerception( owner->GetActor(), pet ); } break; case psPETCommandMessage::CMD_ASSIST : break; case psPETCommandMessage::CMD_GUARD : break; case psPETCommandMessage::CMD_NAME : // apply name change to pet. familiarID = owner->GetCharacterData()->familiar_id; if ( !familiarID ) { psserver->SendSystemInfo(me->clientnum,"You have no familiar to command."); return; } if ( owner->GetFamiliar() ) { pet = dynamic_cast (owner->GetFamiliar()); } else { EntityManager::GetSingleton().CreateNPC( familiarID ); pet = GEMSupervisor::GetSingleton().FindNPCEntity( familiarID ); } if (pet == NULL) { Error2("Error while finding Familiar NPC for Character '%s'\n", owner->GetCharacterData()->GetCharName()); psserver->SendSystemError( me->clientnum, "Could not find Familiar GEM."); return; } if ( words.GetCount() == 0 ) { psserver->SendSystemInfo( me->clientnum, "You must specify a new name for your pet." ); return; } firstName = words.Get( 0 ); firstName = NormalizeCharacterName( firstName ); if ( !psServerCharManager::FilterName( firstName ) ) { psserver->SendSystemError( me->clientnum, "The name %s is invalid!", firstName.GetData() ); return; } if ( words.GetCount() > 1 ) { lastName = words.GetTail( 1 ); lastName = NormalizeCharacterName( lastName ); if ( !psServerCharManager::FilterName( lastName ) ) { psserver->SendSystemError( me->clientnum, "The last name %s is invalid!", lastName.GetData() ); return; } } else { lastName = ""; } chardata = pet->GetCharacterData(); prevFirstName = chardata->GetCharName(); prevLastName = chardata->GetCharLastName(); if ( firstName == prevFirstName && lastName == prevLastName ) { // no changes needed return; } //Querying the DB is slow, but I don't see another way to do it { Result rsCheckDuplicate( db->Select("SELECT * FROM characters WHERE name='%s'", firstName.GetDataSafe() ) ); if (rsCheckDuplicate.IsValid() && rsCheckDuplicate.Count() > 0) { psserver->SendSystemError( me->clientnum, "The name %s is not unique!", firstName.GetDataSafe() ); return; } } prevFullName = chardata->GetCharFullName(); chardata->SetFullName( firstName, lastName ); fullName = chardata->GetCharFullName(); psServer::CharacterLoader.SaveCharacterData( chardata, pet, true ); if ( owner->GetFamiliar() ) { psUpdateObjectNameMessage newNameMsg(0,pet->GetEntity()->GetID(),pet->GetCharacterData()->GetCharFullName()); psserver->GetEventManager()->Broadcast(newNameMsg.msg,NetBase::BC_EVERYONE); } else { EntityManager::GetSingleton().RemoveActor( pet ); } psserver->SendSystemInfo( me->clientnum, "Your pet %s is now known as %s", prevFullName.GetData(), fullName.GetData()); break; case psPETCommandMessage::CMD_TARGET : //pet = dynamic_cast (owner->GetFamiliar()); if ( pet != NULL ) { if ( words.GetCount() == 0 ) { psserver->SendSystemInfo( me->clientnum, "You must specify a name for your pet to target." ); return; } firstName = words.Get( 0 ); if ( words.GetCount() > 1 ) lastName = words.GetTail( 1 ); firstName = NormalizeCharacterName( firstName ); lastName = NormalizeCharacterName( lastName ); PS_ID target_id = psServer::CharacterLoader.FindCharacterID( firstName, false ); if ( target_id ) { gemObject *target = GEMSupervisor::GetSingleton().FindPlayerEntity( target_id ); if ( target ) { pet->SetTarget( target ); psserver->SendSystemInfo( me->clientnum, "%s has successfully targeted %s." , pet->GetName(), firstName.GetData() ); } } else { psserver->SendSystemInfo( me->clientnum, "Cannot find '%s' to target.", firstName.GetData() ); } } break; } } void NPCManager::PrepareMessage() { outbound = new psNPCCommandsMessage(0,15000); cmd_count = 0; } /** * Talking sends the speaker, the target of the speech, and * the worst faction score between the two to the superclient. */ void NPCManager::QueueTalkPerception(gemActor *speaker,gemNPC *target) { float faction = target->GetRelativeFaction(speaker); outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_TALK); outbound->msg->Add( (uint32_t) speaker->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) target->GetEntity()->GetID() ); outbound->msg->Add( (int16_t) faction ); cmd_count++; Debug4(LOG_NPC, speaker->GetEntity()->GetID(),"Added perception: %s spoke to %s with %1.1f faction standing.\n", speaker->GetEntity()->GetName(), target->GetEntity()->GetName(), faction); } /** * The initial attack perception sends group info to the superclient * so that the npc can populate his hate list. Then future damage * perceptions can influence this hate list to help the mob decide * who to attack back. The superclient can also use this perception * to "cheat" and not wait for first damage to attack back--although * we are currently not using it that way. */ void NPCManager::QueueAttackPerception(gemActor *attacker,gemNPC *target) { if (attacker->InGroup()) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_GROUPATTACK); outbound->msg->Add( (uint32_t) target->GetEntity()->GetID() ); csRef g = attacker->GetGroup(); outbound->msg->Add( (int8_t) g->GetMemberCount() ); for (int i=0; i<(int)g->GetMemberCount(); i++) { outbound->msg->Add( (uint32_t) g->GetMember(i)->GetEntity()->GetID() ); outbound->msg->Add( (int8_t) g->GetMember(i)->GetCharacterData()->GetSkills()->GetBestSkillSlot(true)); } cmd_count++; Debug3(LOG_NPC, attacker->GetEntity()->GetID(),"Added perception: %s's group is attacking %s.\n", attacker->GetEntity()->GetName(), target->GetEntity()->GetName() ); } else // lone gunman { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_ATTACK); outbound->msg->Add( (uint32_t) target->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) attacker->GetEntity()->GetID() ); cmd_count++; Debug3(LOG_NPC, attacker->GetEntity()->GetID(),"Added perception: %s is attacking %s.\n", attacker->GetEntity()->GetName(), target->GetEntity()->GetName() ); } } /** * Each instance of damage to an NPC is sent here. The NPC is expected * to use this information on his hate list. */ void NPCManager::QueueDamagePerception(gemActor *attacker,gemNPC *target,float dmg) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_DMG); outbound->msg->Add( (uint32_t) attacker->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) target->GetEntity()->GetID() ); outbound->msg->Add( (float) dmg ); cmd_count++; Debug4(LOG_NPC, attacker->GetEntity()->GetID(),"Added perception: %s hit %s for %1.1f dmg.\n", attacker->GetEntity()->GetName(), target->GetEntity()->GetName(), dmg); } void NPCManager::QueueDeathPerception(gemObject *who) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_DEATH); outbound->msg->Add( (uint32_t) who->GetEntity()->GetID() ); cmd_count++; Debug2(LOG_NPC, who->GetEntity()->GetID(),"Added perception: %s death.\n",who->GetName()); } void NPCManager::QueueSpellPerception(gemActor *caster, gemObject *target,const char *spell_cat_name, uint32_t spell_category, float severity) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_SPELL); outbound->msg->Add( (uint32_t) caster->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) target->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) spell_category ); outbound->msg->Add( (int8_t) (severity * 10) ); cmd_count++; Debug4(LOG_NPC, caster->GetEntity()->GetID(),"Added perception: %s cast a %s spell on %s.\n",caster->GetName(), spell_cat_name, target->GetName() ); } void NPCManager::QueueEnemyPerception(psNPCCommandsMessage::PerceptionType type, gemActor *npc, gemActor *player, float relative_faction) { outbound->msg->Add( (int8_t) type); outbound->msg->Add( (uint32_t) npc->GetEntity()->GetID() ); // Only entity IDs are passed to npcclient outbound->msg->Add( (uint32_t) player->GetEntity()->GetID() ); outbound->msg->Add( (float) relative_faction); cmd_count++; Debug5(LOG_NPC, player->GetEntity()->GetID(),"Added perception: Entity %d within range of entity %d, type %d, faction %.0f.\n",player,npc,type,relative_faction ); gemNPC *myNPC = dynamic_cast(npc); if (!myNPC) return; // Illegal to not pass actual npc object to this function myNPC->ReactToPlayerApproach(type, player); } /** * The client /pet stay command cause the OwnerCmdStay perception to be sent to * the superclient. */ void NPCManager::QueueOwnerCmdStayPerception(gemActor *owner, gemNPC *pet) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_OWNER_CMD ); outbound->msg->Add( (uint32_t) psPETCommandMessage::CMD_STAY ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) pet->GetEntity()->GetID() ); cmd_count++; Debug3(LOG_NPC, owner->GetEntity()->GetID(),"Added perception: %s has told %s to stay.\n", owner->GetEntity()->GetName(), pet->GetEntity()->GetName() ); } /** * The client /pet follow command cause the OwnerCmdFollow perception to be sent to * the superclient. */ void NPCManager::QueueOwnerCmdFollowPerception(gemActor *owner, gemNPC *pet) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_OWNER_CMD ); outbound->msg->Add( (uint32_t) psPETCommandMessage::CMD_FOLLOW ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) pet->GetEntity()->GetID() ); cmd_count++; Debug3(LOG_NPC, owner->GetEntity()->GetID(), "Added perception: %s has told %s to follow.\n", owner->GetEntity()->GetName(), pet->GetEntity()->GetName() ); } /** * The client /pet attack command cause the OwnerCmdAttack perception to be sent to * the superclient. */ void NPCManager::QueueOwnerCmdAttackPerception(gemActor *owner, gemNPC *pet) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_OWNER_CMD ); outbound->msg->Add( (uint32_t) psPETCommandMessage::CMD_ATTACK ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) pet->GetEntity()->GetID() ); cmd_count++; Debug3(LOG_NPC, owner->GetEntity()->GetID(),"Added perception: %s has told %s to attack.\n", owner->GetEntity()->GetName(), pet->GetEntity()->GetName() ); } /** * The client /pet follow command cause the OwnerCmdStopAttack perception to be sent to * the superclient. */ void NPCManager::QueueOwnerCmdStopAttackPerception(gemActor *owner, gemNPC *pet) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_OWNER_CMD ); outbound->msg->Add( (uint32_t) psPETCommandMessage::CMD_STOPATTACK ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); outbound->msg->Add( (uint32_t) pet->GetEntity()->GetID() ); cmd_count++; Debug3(LOG_NPC, owner->GetEntity()->GetID(),"Added perception: %s has told %s to stop atttacking.\n", owner->GetEntity()->GetName(), pet->GetEntity()->GetName() ); } void NPCManager::QueueInventoryPerception(gemActor *owner, psItem * itemdata, bool inserted) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_INVENTORY ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); outbound->msg->Add( (char*) itemdata->GetName() ); outbound->msg->Add( (bool) inserted ); cmd_count++; Debug6(LOG_NPC, owner->GetEntity()->GetID(),"Added perception: %s(%u) has %s %s %s inventory.\n", owner->GetEntity()->GetName(), owner->GetEntity()->GetID(), (inserted?"added":"removed"), itemdata->GetName(), (inserted?"to":"from") ); } void NPCManager::QueueFlagPerception(gemActor *owner) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::PCPT_FLAG ); outbound->msg->Add( (uint32_t) owner->GetEntity()->GetID() ); uint32_t flags = 0; if (!owner->GetVisibility()) flags |= psNPCCommandsMessage::INVISIBLE; if (owner->GetInvincibility()) flags |= psNPCCommandsMessage::INVINCIBLE; outbound->msg->Add( flags ); cmd_count++; Debug4(LOG_NPC, owner->GetEntity()->GetID(),"Added perception: %s(%u) flags 0x%X.\n", owner->GetEntity()->GetName(), owner->GetEntity()->GetID(), flags); } void NPCManager::SendAllCommands() { if (cmd_count) { outbound->msg->Add( (int8_t) psNPCCommandsMessage::CMD_TERMINATOR); if (outbound->msg->overrun) { Bug1("Failed to terminate and send psNPCCommandsMessage. Would have overrun buffer.\n"); } else { outbound->msg->ClipToCurrentSize(); // CPrintf(CON_DEBUG, "Sending %d bytes to superclients...\n",outbound->msg->data->GetTotalSize() ); outbound->Multicast(superclients,-1,PROX_LIST_ANY_RANGE); } delete outbound; outbound=NULL; PrepareMessage(); } psNPCManagerTick *tick = new psNPCManagerTick(NPC_TICK_INTERVAL,this); eventmanager->Push(tick); } void NPCManager::NewNPCNotify(gemNPC *npc,int master_id) { /*Debug5(LOG_NPC,*/ printf("New NPC Entity %d sent to superclients.\n",npc->GetPlayerID() ); psNewNPCCreatedMessage msg(0,npc->GetPlayerID(),master_id); msg.Multicast(superclients,-1,PROX_LIST_ANY_RANGE); } void NPCManager::ControlNPC( gemNPC* npc ) { Client* superclient = clients->FindAccount( npc->GetSuperclientID() ); if(superclient) { npc->Send(superclient->GetClientNum(), false, false ); npc->GetCharacterData()->SetImperviousToAttack(npc->GetCharacterData()->GetImperviousToAttack() & ~TEMPORARILY_IMPERVIOUS); // may switch this to 'hide' later } else npc->GetCharacterData()->SetImperviousToAttack(npc->GetCharacterData()->GetImperviousToAttack() | TEMPORARILY_IMPERVIOUS); // may switch this to 'hide' later } void NPCManager::SetNPCOwner(gemNPC *npc,int owner_id) { /*Debug5(LOG_NPC,*/ printf("Setting NPC %d to Owner %d sent to superclients.\n",npc->GetPlayerID(), owner_id ); gemActor *owner = (gemActor *)GEMSupervisor::GetSingleton().FindObject( owner_id ); if ( owner ) { psNPCSetOwnerMessage msg( 0, npc->GetPlayerID(), owner_id ); msg.Multicast(superclients,-1,PROX_LIST_ANY_RANGE); } } PetOwnerSession *NPCManager::CreatePetOwnerSession( gemActor *owner, psCharacter* petData ) { printf("Adding Owner Session.\n"); if ( owner && petData ) { PetOwnerSession *session = new PetOwnerSession( this, owner, petData ); if ( session ) { OwnerPetList.Insert( session ); return session; } } return NULL; } void NPCManager::RemovePetOwnerSession( PetOwnerSession *session) { printf("Removing Owner Session.\n"); if ( session ) { OwnerPetList.Delete( session ); delete session; } } void NPCManager::UpdatePetTime() { PetOwnerSession *po; // Loop through all Sessions BinaryRBIterator< PetOwnerSession > loop( &OwnerPetList ); bool found = false; for ( po = loop.First(); po; po = ++loop ) // Increase Pet Time in Game by NPC_TICK_INTERVAL { if ( po->isActive ) { po->UpdateElapsedTime( NPC_TICK_INTERVAL ); //if ( po->owner ) // CPrintf(CON_NOTIFY,"updated pet time ( %s, %d, %.2f )\n", po->owner->GetName(), po->petID, po->elapsedTime); } // Check session for time up } } /*------------------------------------------------------------------*/ psNPCManagerTick::psNPCManagerTick(int offsetticks, NPCManager *c) : psGameEvent(0,offsetticks,"psNPCManagerTick") { npcmgr = c; } void psNPCManagerTick::Trigger() { static int counter=0; if (!(counter%5)) npcmgr->UpdateWorldPositions(); npcmgr->SendAllCommands(); npcmgr->UpdatePetTime(); counter++; } /*-----------------------------------------------------------------*/ /* Pet Skills Handler */ /*-----------------------------------------------------------------*/ void NPCManager::HandlePetSkill( MsgEntry * me ) { psPetSkillMessage msg( me ); if ( !msg.valid ) { Debug2( LOG_NET, me->clientnum, "Received unparsable psPetSkillMessage from client %u.\n", me->clientnum ); return; } Client* client = clients->FindAny( me->clientnum ); if ( !client->GetFamiliar() ) return; // CPrintf(CON_DEBUG, "ProgressionManager::HandleSkill(%d,%s)\n",msg.command, (const char*)msg.commandData); switch ( msg.command ) { case psPetSkillMessage::REQUEST: { // Send all petStats to seed client psStatDRMessage msgStats( me->clientnum, client->GetFamiliar()->GetEntity()->GetID(), client->GetFamiliar()->GetCharacterData(), DIRTY_VITAL_ALL); if (msgStats.valid) eventmanager->SendMessage(msgStats.msg); SendPetSkillList( client ); break; } case psPetSkillMessage::SKILL_SELECTED: { csRef xml = csPtr(new csTinyDocumentSystem); CS_ASSERT( xml ); csRef invList = xml->CreateDocument(); const char* error = invList->Parse( msg.commandData ); if ( error ) { Error2("Error in XML: %s", error ); return; } csRef root = invList->GetRoot(); if(!root) { Error1("No XML root"); return; } csRef topNode = root->GetNode("S"); if(!topNode) { Error1("No tag"); return; } csString skillName = topNode->GetAttributeValue( "NAME" ); psSkillInfo *info = CacheManager::GetSingleton().GetSkillByName( skillName ); csString buff; if (info) { csString escpxml = EscpXML( info->description ); buff.Format( "", escpxml.GetData(), info->category); } else { buff.Format( "", info->category ); } psCharacter* chr = client->GetFamiliar()->GetCharacterData(); psPetSkillMessage newmsg(client->GetClientNum(), psPetSkillMessage::DESCRIPTION, buff, int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA)), int(chr->GetHP()), int(chr->GetMana()), int(chr->GetStamina(true)), int(chr->GetStamina(false)), int(chr->GetHitPointsMax()), int(chr->GetManaMax()), int(chr->GetStaminaMax(true)), int(chr->GetStaminaMax(false)), float(chr->duel_points), true, PSSKILL_NONE); if (newmsg.valid) eventmanager->SendMessage(newmsg.msg); else { Bug2("Could not create valid psPetSkillMessage for client %u.\n",client->GetClientNum()); } break; } // case psPetSkillMessage::BUY_SKILL: // { // CPrintf(CON_DEBUG, "---------------Buying Skill-------------\n"); // csRef xml = csPtr(new csTinyDocumentSystem); // CS_ASSERT( xml ); // // csRef invList = xml->CreateDocument(); // const char* error = invList->Parse( msg.commandData); // if ( error ) // { // Error2("Error in XML: %s", error ); // CPrintf(CON_DEBUG, " Error in XML\n"); // return; // } // csRef root = invList->GetRoot(); //if(!root) //{ // Error1("No XML root"); // return; //} // csRef topNode = root->GetNode("B"); //if(!topNode) //{ // Error1("No tag"); // return; //} // // csString skillName = topNode->GetAttributeValue("NAME"); //if(!skillName || (skillName=="")) //{ // psserver->SendSystemError(client->GetClientNum(), "You don't have any skill selected."); // return; //} // psSkillInfo * info = CacheManager::GetSingleton().GetSkillByName(skillName); // CPrintf(CON_DEBUG, " Looking for: %s\n", (const char*)skillName); // // if (!info) // { // Error2("No skill with name %s found!",skillName.GetData()); // Error2("Full Data Sent from Client was: %s\n", msg.commandData.GetData() ); // CPrintf(CON_DEBUG, " No Skill Found\n"); // return; // } // psCharacter * character = client->GetCharacterData(); // if (character->GetTrainer() == NULL) // { // psserver->SendSystemInfo(client->GetClientNum(), // "Can't buy skills when not training!"); // return; // } // // gemActor* actorTrainer = character->GetTrainer()->GetActor(); // if ( actorTrainer ) // { // if ( character->GetActor()->RangeTo(actorTrainer) > RANGE_TO_SELECT ) // { // psserver->SendSystemInfo(client->GetClientNum(), // "Need to get a bit closer to understand the training."); // return; // } // } // // // CPrintf(CON_DEBUG, " PP available: %d\n", character->GetProgressionPoints() ); // // // Test for progression points // if (character->GetProgressionPoints() <= 0) // { // psserver->SendSystemInfo(client->GetClientNum(), // "You don't have any progression points!"); // return; // } // // Test for money // // if (info->price > character->Money()) // { // psserver->SendSystemInfo(client->GetClientNum(), // "You don't have the money to buy this skill!"); // return; // } // if ( !character->CanTrain( info->id ) ) // { // psserver->SendSystemInfo(client->GetClientNum(), // "You cannot train this skill any higher yet!"); // return; // } // // int current = character->GetSkills()->GetSkillRank((PSSKILL)info->id); // float faction = actorTrainer->GetRelativeFaction(character->GetActor()); // if ( !character->GetTrainer()->GetTrainerInfo()->TrainingInSkill((PSSKILL)info->id, current, faction)) // { // psserver->SendSystemInfo(client->GetClientNum(), // "You cannot train this skill currently."); // return; // } // character->UseProgressionPoints(1); // character->SetMoney(character->Money()-info->price); // character->Train(info->id,1); // SendSkillList(client,true,info->id); // // psserver->SendSystemInfo(client->GetClientNum(), "You've received some %s training", skillName.GetData()); // // break; // } case psPetSkillMessage::QUIT: { //client->GetCharacterData()->SetTrainer(NULL); break; } } } void NPCManager::SendPetSkillList(Client * client, bool forceOpen, PSSKILL focus ) { psString buff; psCharacter * character = client->GetFamiliar()->GetCharacterData(); psCharacter * trainer = character->GetTrainer(); psTrainerInfo * trainerInfo = NULL; float faction = 0.0; buff.Format("", character->GetProgressionPoints()); if (trainer) { trainerInfo = trainer->GetTrainerInfo(); faction = trainer->GetActor()->GetRelativeFaction(character->GetActor()); } int realID = -1; bool found = false; for (int skillID = 0; skillID < (int)PSSKILL_COUNT; skillID++) { psSkillInfo * info = CacheManager::GetSingleton().GetSkillByID(skillID); if (!info) { Error2("Can't find skill %d",skillID); return; } Skill * charSkill = character->GetSkills()->GetSkill( (PSSKILL)skillID ); if (charSkill == NULL) { Error3("Can't find skill %d in character %i",skillID, character->characterid); return; } // // If we are training, send skills that the trainer is providing education in only // if ( // !trainerInfo // || // trainerInfo->TrainingInSkill((PSSKILL)skillID, character->GetSkills()->GetSkillRank((PSSKILL)skillID), faction) // ) // { // bool stat = info->id == PSSKILL_AGI || // info->id == PSSKILL_CHA || // info->id == PSSKILL_END || // info->id == PSSKILL_INT || // info->id == PSSKILL_WILL || // info->id == PSSKILL_STR; // if(!found) // Get the row id the client will use // { // realID++; // if(info->id == focus) // found = true; // } csString escpxml = EscpXML(info->name); buff.AppendFmt("", escpxml.GetData(), charSkill->rank, charSkill->y, charSkill->yCost, charSkill->z, charSkill->zCost, info->category); //} } buff.Append(""); psCharacter* chr = client->GetFamiliar()->GetCharacterData(); psPetSkillMessage newmsg(client->GetClientNum(), psPetSkillMessage::SKILL_LIST, buff, (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH), (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE), (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY), (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE), (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL), (unsigned int)chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA), (unsigned int)chr->GetHP(), (unsigned int)chr->GetMana(), (unsigned int)chr->GetStamina(true), (unsigned int)chr->GetStamina(false), (unsigned int)chr->GetHitPointsMax(), (unsigned int)chr->GetManaMax(), (unsigned int)chr->GetStaminaMax(true), (unsigned int)chr->GetStaminaMax(false), chr->duel_points, forceOpen, found?realID:-1 ); CPrintf(CON_DEBUG, "Sending psPetSkillMessage w/ stats to %d, Valid: ",int(client->GetClientNum())); if (newmsg.valid) { CPrintf(CON_DEBUG, "Yes\n"); eventmanager->SendMessage(newmsg.msg); } else { CPrintf(CON_DEBUG, "No\n"); Bug2("Could not create valid psPetSkillMessage for client %u.\n",client->GetClientNum()); } }