/* * usermanager.cpp - Author: Keith Fulton * * Copyright (C) 2001 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 /////////////////////////////////////////////////////////////////////////////// // CS INCLUDES /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // PLANESHIFT INCLUDES /////////////////////////////////////////////////////////////////////////////// #include "util/serverconsole.h" #include "util/pserror.h" #include "util/psconst.h" #include "util/log.h" #include "engine/celbase.h" #include "engine/netpersist.h" #include "engine/drmessage.h" #include "client.h" #include "clients.h" #include "events.h" #include "gem.h" #include "netmanager.h" #include "entitymanager.h" #include "marriagemanager.h" #include "combatmanager.h" #include "invitemanager.h" #include "usermanager.h" #include "adminmanager.h" #include "commandmanager.h" #include "psserver.h" #include "cachemanager.h" #include "playergroup.h" #include "globals.h" #include "progressionmanager.h" #include "util/eventmanager.h" #include "netmanager.h" #include "advicemanager.h" #include "bulkobjects/pscharacter.h" #include "bulkobjects/psraceinfo.h" #include "bulkobjects/psguildinfo.h" #include "bulkobjects/pscharacterloader.h" #include "chatmanager.h" #define RANGE_TO_CHALLENGE 50 class psUserStatRegeneration : public psGameEvent { protected: UserManager * usermanager; public: psUserStatRegeneration(UserManager *mgr,csTicks ticks); virtual void Trigger(); // Abstract event processing function }; /** A structure to hold the clients that are pending on duel challenges. */ class PendingDuelInvite : public PendingInvite { public: PendingDuelInvite(Client *inviter, Client *invitee, const char *question) : PendingInvite( inviter, invitee, true, question,"Accept","Decline", "You have challenged %s to a duel.", "%s has challenged you to a duel.", "%s has accepted your challenge.", "You have accepted %s's challenge.", "%s has declined your challenge.", "You have declined %s's challenge.", psQuestionMessage::duelConfirm) { } virtual ~PendingDuelInvite() {} void HandleAnswer(const csString & answer); }; /***********************************************************************/ UserManager::UserManager(ClientConnectionSet *cs) { clients = cs; psserver->GetEventManager()->Subscribe(this,MSGTYPE_USERCMD,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_MOTDREQUEST,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_CHARDETAILSREQUEST,REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_CHARDESCUPDATE,REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this,MSGTYPE_TARGET_EVENT,NO_VALIDATION); } UserManager::~UserManager() { if (psserver->GetEventManager()) { psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_USERCMD); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_MOTDREQUEST); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_CHARDETAILSREQUEST); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_CHARDESCUPDATE); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_TARGET_EVENT); } } void UserManager::HandleMOTDRequest(MsgEntry *me,Client *client) { //Sends MOTD and tip unsigned int guildID =0; //If data isn't loaded, load from db if (!client->GetCharacterData()) { Result result(db->Select("SELECT guild_member_of FROM characters WHERE id = '%d'",client->GetPlayerID())); if (result.Count() > 0) guildID = result[0].GetUInt32(0); } else { psGuildInfo* playerGuild = client->GetCharacterData()->GetGuild(); if (playerGuild) guildID = playerGuild->id; else guildID =0; } csString tip = ""; if (CacheManager::GetSingleton().GetTipLength() > 0) CacheManager::GetSingleton().GetTipByID(psserver->GetRandom(CacheManager::GetSingleton().GetTipLength()), tip ); csString motdMsg(psserver->GetMOTD()); csString guildMotd(""); csString guildName(""); psGuildInfo * guild = CacheManager::GetSingleton().FindGuild(guildID); if (guild) { guildMotd=guild->GetMOTD(); guildName=guild->GetName(); } psMOTDMessage motd(me->clientnum,tip,motdMsg,guildMotd,guildName); motd.SendMessage(); return; } void UserManager::HandleUserCommand(MsgEntry *me,Client *client) { if (client->IsFrozen()) //disable most commands return; psUserCmdMessage msg(me); Debug3(LOG_USER, client->GetClientNum(),"Received user command: %s from %s\n", me->bytes->payload, (const char *)client->GetName()); if (!msg.valid) { psserver->SendSystemError(me->clientnum,"Command not supported by server yet."); return; } if (msg.command == "/who") { Who(msg,client,me->clientnum); } else if (msg.command == "/buddy") { Buddy(msg,client,me->clientnum); } else if (msg.command == "/notbuddy") { NotBuddy(msg,client, me->clientnum); } else if (msg.command == "/buddylist") { BuddyList(client,me->clientnum, UserManager::ALL_PLAYERS); } else if (msg.command == "/roll") { RollDice(msg,client,me->clientnum); } else if (msg.command == "/pos") { ReportPosition(msg,client,me->clientnum); } /*else if (msg.command == "/spawn") { MoveToSpawnPos(msg,client,me->clientnum);* }*/ else if (msg.command == "/unstick") { MoveToValidPos(msg,client,me->clientnum); } else if (msg.command == "/attack") { HandleAttack(msg,client,me->clientnum); } else if (msg.command == "/stopattack") { StopAttack(msg,client,me->clientnum); } else if ( msg.command == "/admin" ) { psserver->GetAdminManager()->Admin(client->GetPlayerID(), me->clientnum, client); } else if ( msg.command == "/loot" ) { HandleLoot(client); } else if ( msg.command == "/quests" ) { HandleQuests(client); } else if ( msg.command == "/train" ) { HandleTraining(client); } else if (msg.command == "/greet" ) { psSystemMessage newmsg(me->clientnum, MSG_INFO_BASE, "%s greets %s.", client->GetActor()->GetName(), (client->GetTargetObject())?client->GetTargetObject()->GetName():"everyone" ); newmsg.Multicast(client->GetActor()->GetMulticastClients(),0,CHAT_SAY_RANGE); psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(),"greet"); anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE ); } else if (msg.command == "/sit" ) { if ((msg.filter == "down" || msg.filter == "") && client->GetActor()->GetMode() == PSCHARACTER_MODE_PEACE) { client->GetActor()->SetMode(PSCHARACTER_MODE_SIT); psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(), "sit"); anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE ); } else if ((msg.filter == "up" || msg.filter == "") && client->GetActor()->GetMode() == PSCHARACTER_MODE_SIT) { client->GetActor()->SetMode(PSCHARACTER_MODE_PEACE); psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(), "stand up"); anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE ); } } else if (msg.command == "/starttrading") { client->GetCharacterData()->SetTradingStopped(false); psserver->SendSystemInfo(me->clientnum,"You can trade now."); } else if (msg.command == "/stoptrading") { client->GetCharacterData()->SetTradingStopped(true); psserver->SendSystemInfo(me->clientnum,"You are busy and can't trade."); } else if ( msg.command == "/assist" ) { Assist( msg, client, me->clientnum ); } /* else if (msg.command == "/advisormode") { Advisor(client, me->clientnum, msg); } */ else if (msg.command == "/tip") { GiveTip(me->clientnum); } else if (msg.command == "/motd") { GiveMOTD(me->clientnum); } else if (msg.command == "/challenge") { ChallengeToDuel(msg,client); } else if (msg.command == "/yield") { YieldDuel(msg,client); } else if (msg.command == "/duelpoints") { csString str; str.Format("You currently have %1.2f Duel Points.",client->GetCharacterData()->duel_points); psserver->SendSystemInfo(me->clientnum, str); } else if ( msg.command == "/die" ) { gemActor* actor = client->GetActor(); if ( client->GetDuelClientCount() != 0 ) YieldDuel(msg, client); if (!actor) return; actor->Kill(actor); } else if (msg.command == "/marriage") { if (msg.action == "propose") { if ( msg.player.IsEmpty() || msg.text.IsEmpty() ) { psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage propose [first name] [message]" ); return; } // Send propose message psserver->GetMarriageManager()->Propose(client, msg.player, msg.text); } else if (msg.action == "divorce") { if ( msg.text.IsEmpty() ) { psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage divorce [message]" ); return; } // Send divorce prompt psserver->GetMarriageManager()->ContemplateDivorce(client, msg.text); } else { psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage [propose|divorce]" ); } } else { psserver->SendSystemError(me->clientnum,"Command not supported by server yet."); } } void UserManager::HandleCharDetailsRequest(MsgEntry *me,Client *client) { psCharacterDetailsRequestMessage msg(me); gemActor *myactor; if (!msg.isMe) { gemObject *target = client->GetTargetObject(); if (!target) return; myactor = target->GetActorPtr(); if (!myactor) return; } else { myactor = client->GetActor(); if (!myactor) return; } psCharacter* charData = myactor->GetCharacterData(); if (!charData) return; SendCharacterDescription(client, charData, false, msg.isSimple, msg.requestor); } csString intToStr(int f) { csString s; s.Format("%i", f); return s; } void UserManager::SendCharacterDescription(Client * client, psCharacter * charData, bool full, bool simple, const csString & requestor) { StatSet* playerAttr = client->GetCharacterData()->GetAttributes(); StatSet* charAttr = charData->GetAttributes(); bool isSelf = (charData->GetCharacterID() == client->GetCharacterData()->GetCharacterID()); csString charName = charData->GetCharFullName(); csString raceName = charData->GetRaceInfo()->name; csString desc = charData->GetDescription(); if ( !simple && CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "view stats") ) full = true; // GMs can view the stats list if (full) { desc += "\n\n"; desc += "HP: "+intToStr(int(charData->GetHP()))+" Max HP: "+intToStr(int(charData->GetHitPointsMax()))+"\n"; desc += "STR: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_STRENGTH)))+"\n"; desc += "END: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_ENDURANCE)))+"\n"; desc += "AGI: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_AGILITY)))+"\n"; desc += "INT: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE)))+"\n"; desc += "WIL: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_WILL)))+"\n"; desc += "CHA: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_CHARISMA)))+"\n"; } // No fancy things if simple description is requested if ( !simple ) { // Don't guess strength if we can't attack the character or if he's // dead or if we are viewing our own description if ( !charData->impervious_to_attack && (charData->GetMode() != PSCHARACTER_MODE_DEAD) && !isSelf ) { if ( playerAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE) < 50 ) desc.AppendFmt( "\n\nYou try to evaluate the strength of %s, but you have no clue.", charName.GetData() ); else { // Character's Strength assessment code below. static const char* const StrengthGuessPhrases[] = { "won't require any effort to defeat", "is noticeably weaker than you", "won't pose much of a challenge", "is not quite as strong as you", "is about as strong as you", "is somewhat stronger than you", "will pose a challenge to defeat", "is significantly more powerful than you", "may be impossible to defeat" }; bool smart = (playerAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE) >= 100); int CharsLvl = charData->GetCharLevel(); int PlayersLvl = client->GetCharacterData()->GetCharLevel(); int LvlDifference = PlayersLvl - CharsLvl; int Phrase = 0; if ( LvlDifference >= 50 ) Phrase = 0; else if ( LvlDifference > 30 ) Phrase = 1; else if ( LvlDifference > 15 && smart) Phrase = 2; else if ( LvlDifference > 5 ) Phrase = 3; else if ( LvlDifference >= -5 && LvlDifference <= 5 ) Phrase = 4; else if ( LvlDifference >= -15 ) Phrase = 5; else if ( LvlDifference >= -30 && smart) Phrase = 6; else if ( LvlDifference >= -45 ) Phrase = 7; else if ( LvlDifference < -50 ) Phrase = 8; // Enable for Debugging only // desc+="\n CharsLvl: "; desc+=CharsLvl; desc+=" | YourLvl: "; desc+=PlayersLvl; desc+="\n"; desc.AppendFmt( "\n\nYou evaluate that %s %s.", charName.GetData(), StrengthGuessPhrases[Phrase] ); } } // Show spouse name if character is married if ( charData->GetIsMarried() && !isSelf ) desc.AppendFmt( "\n\nMarried to: %s", charData->GetSpouseName() ); // Show owner name if character is a pet if ( charData->IsPet() && charData->GetOwnerID() ) { gemActor *owner = GEMSupervisor::GetSingleton().FindPlayerEntity( charData->GetOwnerID() ); if (owner) desc.AppendFmt( "\n\nA pet owned by: %s", owner->GetName() ); } } // Finally send the details message psCharacterDetailsMessage detailmsg(client->GetClientNum(), charName, (short unsigned int)charData->GetRaceInfo()->gender, raceName, desc, requestor ); detailmsg.SendMessage(); } void UserManager::HandleCharDescUpdate(MsgEntry *me,Client *client) { psCharacterDescriptionUpdateMessage descUpdate(me); psCharacter* charData = client->GetCharacterData(); if (!charData) return; charData->SetDescription(descUpdate.newValue); Debug3(LOG_USER, client->GetClientNum(), "Character description updated for %s (%d)\n",charData->GetCharFullName(),client->GetAccountID()); } void UserManager::HandleDeathEvent(MsgEntry *me) { Debug1(LOG_COMBAT, 0,"UserManager handling Death Event\n"); // UserManager only stops duels and awards credits on death events psDeathEvent evt(me); Client *loser=NULL,*winner=NULL; loser = clients->Find( evt.deadActor->GetClientID() ); if(evt.killer) winner = clients->Find( evt.killer->GetClientID() ); csString loserName("None"); csString winnerName("the world"); if(loser && loser->GetActor()) loserName = loser->GetName(); if(winner && winner->GetActor()) winnerName = winner->GetName(); csString info; if ( loser == winner ) { info.Format("%s has just killed themself", loserName.GetData()); } else { info.Format("%s has just been killed by %s", loserName.GetData(), winnerName.GetData()); } // Remove every attacker if (loser) { for(int i = loser->GetDuelClientCount()-1; i >= 0;i--) { Client* client = clients->Find(loser->GetDuelClient(i)); if(!client) { loser->RemoveDuelClient(loser->GetDuelClient(i)); continue; } if(client != winner) { psserver->SendSystemInfo(client->GetClientNum(),info); StopDuel(loser,client,false); } else StopDuel(loser,client,true); if(loser->GetTargetObject() == client->GetActor()) { // Stop attacking if we are attacking the killed one psserver->combatmanager->StopAttack(loser->GetActor()); } } } } void UserManager::HandleTargetEvent(MsgEntry *me) { psTargetChangeEvent targetevent(me); Client *targeter = NULL; Client *targeted = NULL; if (targetevent.character) { Debug2(LOG_USER, targetevent.character->GetClientID(),"UserManager handling target event for %s\n", targetevent.character->GetName()); targeter = clients->Find( targetevent.character->GetClientID() ); if (!targeter) return; } if (targetevent.target) targeted = clients->Find( targetevent.target->GetClientID() ); if(targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT) psserver->combatmanager->StopAttack(targeter->GetActor()); if(!targeted && dynamic_cast(targetevent.target) && targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT) // NPC? { if (targeter->IsAllowedToAttack(targetevent.target)) SwitchAttackTarget( targeter, targeted ); return; } else if (!targeted) return; if (targeted->IsReady() && targeter->IsReady() ) { if (targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT) SwitchAttackTarget( targeter, targeted ); } else { psserver->SendSystemError(targeter->GetClientNum(),"Target is not ready yet"); } } void UserManager::HandleMessage(MsgEntry *me,Client *client) { switch (me->GetType()) { case MSGTYPE_MOTDREQUEST: { HandleMOTDRequest(me,client); break; } case MSGTYPE_USERCMD: { HandleUserCommand(me,client); break; } case MSGTYPE_CHARDETAILSREQUEST: { HandleCharDetailsRequest(me,client); break; } case MSGTYPE_CHARDESCUPDATE: { HandleCharDescUpdate(me,client); break; } case MSGTYPE_DEATH_EVENT: { HandleDeathEvent(me); break; } case MSGTYPE_TARGET_EVENT: { HandleTargetEvent(me); break; } } } void UserManager::Who(psUserCmdMessage& msg, Client* client, int clientnum) { csString message((size_t) 1024); csString temp((size_t) 1024); csString headerMsg("Players Currently Online"); if (!msg.filter.IsEmpty()) { size_t pos = msg.filter.FindFirst('%'); while (pos != (size_t) -1) { msg.filter.DeleteAt(pos); pos = msg.filter.FindFirst('%'); } StrToLowerCase(msg.filter); headerMsg.Append(" (Applying Filter: '*"); headerMsg.Append(msg.filter); headerMsg.Append("*')"); } message.Append(headerMsg); headerMsg.Append("\n-------------------------------------"); unsigned count = 0; // Guild rank, guild and title should come from acraig's player prop class. ClientIterator i(*clients); for (Client* curr = i.First(); curr && count<30; curr = i.Next()) { csString playerName(curr->GetName()); csString guildTitle; csString guildName; csString format("%s"); // Player name. if (curr->IsSuperClient() || !curr->GetActor()) continue; psGuildInfo* guild = curr->GetActor()->GetGuild(); if (guild != NULL) { if (guild->id && (!guild->IsSecret() || guild->id == client->GetGuildID())) { psGuildLevel* level = curr->GetActor()->GetGuildLevel(); if (level) { format.Append(", %s in %s"); // Guild level title. guildTitle = level->title; } else { format.Append(", %s"); } guildName = guild->name; } } temp.Format(format.GetData(), curr->GetName(), guildTitle.GetData(), guildName.GetData()); csString lower(temp); StrToLowerCase(lower); if (!msg.filter.IsEmpty() && lower.FindStr(msg.filter) == (size_t)-1) continue; // If the message is too big, send in chunks. if (temp.Length() + message.Length() > 1000) { psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message); newmsg.SendMessage(); message.Clear(); } else message.Append('\n'); message.Append(temp); count++; } // Could be about to overflow by now, so check. temp.Format("%u shown from %u players online\n", count, clients->Count()); if (temp.Length() + message.Length() > 1000) { psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message); newmsg.SendMessage(); message.Clear(); } else message.Append('\n'); message.Append(temp); psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message); newmsg.SendMessage(); } void UserManager::StrToLowerCase(csString& str) { for (register size_t i = 0; i < str.Length(); ++i) str[i] = tolower(str[i]); } void UserManager::Buddy(psUserCmdMessage& msg,Client *client,int clientnum) { msg.player = NormalizeCharacterName(msg.player); if (msg.player.Length() == 0) { psserver->SendSystemError(clientnum,"The character name of your buddy must be specified."); return; } if (client->GetCharacterData()==NULL) { Error3("Client for account '%s' attempted to add buddy '%s' but has no character data!",client->GetName(),msg.player.GetData()); return; } unsigned int selfid=client->GetCharacterData()->GetCharacterID(); bool searchNPCs = false; unsigned int buddyid=psServer::CharacterLoader.FindCharacterID(msg.player.GetData(), searchNPCs); if (buddyid==0) { psserver->SendSystemError(clientnum,"Could not add buddy: Character '%s' not found.", msg.player.GetData()); return; } if ( !client->GetCharacterData()->AddBuddy( buddyid, msg.player ) ) { psserver->SendSystemError(clientnum,"%s could not be added to buddy list.",(const char *)msg.player); return; } Client* buddyClient = clients->FindPlayer( buddyid ); if ( buddyClient && buddyClient->IsReady() ) { buddyClient->GetCharacterData()->BuddyOf( selfid ); } if (!psserver->AddBuddy(selfid,buddyid)) { psserver->SendSystemError(clientnum,"%s is already on your buddy list.",(const char *)msg.player); return; } BuddyList( client, clientnum, true ); psserver->SendSystemInfo(clientnum,"%s has been added to your buddy list.",(const char *)msg.player); } void UserManager::NotBuddy(psUserCmdMessage& msg,Client *client,int clientnum) { msg.player = NormalizeCharacterName(msg.player); if (msg.player.Length() == 0) { psserver->SendSystemError(clientnum,"The character name of your buddy must be specified."); return; } if (client->GetCharacterData()==NULL) { Error3("Client for account '%s' attempted to remove buddy '%s' but has no character data!",client->GetName(),msg.player.GetData()); return; } unsigned int selfid=client->GetCharacterData()->GetCharacterID(); bool searchNPCs = false; unsigned int buddyid=psServer::CharacterLoader.FindCharacterID(msg.player.GetData(), searchNPCs); if (buddyid==0) { psserver->SendSystemError(clientnum,"Could not remove buddy: Character '%s' not found.", msg.player.GetData()); return; } client->GetCharacterData()->RemoveBuddy( buddyid ); Client* buddyClient = clients->FindPlayer( buddyid ); if ( buddyClient ) { psCharacter* buddyChar = buddyClient->GetCharacterData(); if (buddyChar) buddyChar->NotBuddyOf( selfid ); } if (!psserver->RemoveBuddy(selfid,buddyid)) { psserver->SendSystemError(clientnum,"%s is not on your buddy list.",(const char *)msg.player); return; } BuddyList( client, clientnum, true ); psserver->SendSystemInfo(clientnum,"%s has been removed from your buddy list.",(const char *)msg.player); } void UserManager::BuddyList(Client *client,int clientnum,bool filter) { psCharacter *chardata=client->GetCharacterData(); if (chardata==NULL) { Error2("Client for account '%s' attempted to display buddy list but has no character data!",client->GetName()); return; } int totalBuddies = (int)chardata->buddyList.Length(); //psBuddyListMsg should have as parameter a size_t. This is temporary. psBuddyListMsg mesg( clientnum, totalBuddies ); for ( int i = 0; i < totalBuddies; i++ ) { mesg.AddBuddy( i, chardata->buddyList[i].name, (clients->Find(chardata->buddyList[i].name)? true : false) ); } mesg.Build(); mesg.SendMessage(); } void UserManager::NotifyBuddies(Client * client, bool logged_in) { csString name (client->GetName()); for (size_t i=0; i< client->GetCharacterData()->buddyOfList.Length(); i++) { Client *buddy = clients->FindPlayer( client->GetCharacterData()->buddyOfList[i] ); // name of player buddy if (buddy) // is buddy online at the moment? if so let him know buddy just logged on { psBuddyStatus status( buddy->GetClientNum(), name , logged_in ); status.SendMessage(); if (logged_in) { psserver->SendSystemInfo(buddy->GetClientNum(),"%s just joined Planeshift",client->GetName()); } else { psserver->SendSystemInfo(buddy->GetClientNum(),"%s has quit",client->GetName()); } } } } void UserManager::RollDice(psUserCmdMessage& msg,Client *client,int clientnum) { int total=0; if (msg.dice > 10) msg.dice = 10; if (msg.sides > 10000) msg.sides = 10000; if ( msg.dice < 1 ) msg.dice = 1; if ( msg.sides < 1 ) msg.sides = 1; for (int i = 0; irng->Get(msg.sides) + 1; } if (msg.dice > 1) { psSystemMessage newmsg(clientnum,MSG_INFO_BASE, "Player %s has rolled %d %d-sided dice for a %d.", client->GetName(), msg.dice, msg.sides, total); newmsg.Multicast(client->GetActor()->GetMulticastClients(),0, 10); } else { psSystemMessage newmsg(clientnum,MSG_INFO_BASE, "Player %s has rolled a %d-sided die for a %d.", client->GetName(), msg.sides, total); newmsg.Multicast(client->GetActor()->GetMulticastClients(),0, 10); } } void UserManager::ReportPosition(psUserCmdMessage& msg,Client *client,int clientnum) { gemObject *object = NULL; bool self = true; bool extras = CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "pos extras"); // Allow GMs to get other players' and entities' locations if (extras && msg.player.Length()) { self = false; Client* c = psserver->GetAdminManager()->FindPlayerClient(msg.player,0); if (c) object = (gemObject*)c->GetActor(); if (!object) object = psserver->GetAdminManager()->FindObjectByString(msg.player); } else object = client->GetActor(); if (object) { csVector3 pos; iSector* sector = 0; float angle; object->GetPosition(pos, angle, sector); csString sector_name = (sector) ? sector->QueryObject()->GetName() : "(null)"; csString name; if (self) name = "Your"; else name.Format("%s's",object->GetName()); // Report extra info to GMs (players will use skills to determine correct direction) if (extras) { // Get the iRegion this sector belongs to csRef region = SCF_QUERY_INTERFACE(sector->QueryObject()->GetObjectParent(), iRegion); csString region_name = (region) ? region->QueryObject()->GetName() : "(null)"; int degrees = (int)(angle*180.0/PI); psserver->SendSystemInfo(clientnum, "%s current position is %1.2f,%1.2f,%1.2f angle: %d in sector: %s, region: %s", name.GetData(), pos.x, pos.y, pos.z, degrees, sector_name.GetData(), region_name.GetData() ); } else { psserver->SendSystemInfo(clientnum,"%s current position is %1.2f,%1.2f,%1.2f in sector: %s", name.GetData(), pos.x, pos.y, pos.z, sector_name.GetData() ); } } } /*void UserManager::MoveToSpawnPos(psUserCmdMessage& msg, Client *client, int clientnum) { gemActor *actor = client->GetActor(); if (!actor) return; StopAllCombat(client, actor); LogStuck(client, actor); if (actor->MoveToSpawnPos()) { csVector3 pos; float yrot; iSector* sector; actor->GetPosition(pos, yrot, sector); psserver->SendSystemInfo(clientnum, "This command should not be used, please use " "/unstick instead. Moving back to spawn point" " in sector %s...", sector->QueryObject()->GetName()); } else { psserver->SendSystemInfo(clientnum, "Cannot move back to spawn point in null " "sector. Please contact Planeshift support."); } } */ void UserManager::MoveToValidPos(psUserCmdMessage& msg, Client *client, int clientnum) { gemActor *actor = client->GetActor(); if (!actor) return; StopAllCombat(client, actor); LogStuck(client, actor); if (actor->MoveToValidPos()) { csVector3 pos; iSector* sector; actor->GetPosition(pos, sector); psserver->SendSystemInfo(clientnum, "Moving back to valid position in sector %s...", sector->QueryObject()->GetName()); } } void UserManager::LogStuck(Client* client, gemActor* actor) { csVector3 pos; float yrot; iSector* sector; actor->GetPosition(pos, yrot, sector); psRaceInfo* race = actor->GetCharacterData()->GetRaceInfo(); csString buffer; buffer.Format("%s, %s, %d, %s, %.3f, %.3f, %.3f, %.3f", client->GetName(), race->name.GetDataSafe(), race->gender, sector->QueryObject()->GetName(), pos.x, pos.y, pos.z, yrot); psserver->GetLogCSV()->Write(CSV_STUCK, buffer); } void UserManager::StopAllCombat(Client *client, gemActor *actor) { // If we are in a duel, yield. for (int i = client->GetDuelClientCount() - 1; i >= 0; i--) { Client* duel = clients->Find(client->GetDuelClient(i)); if (!duel || !duel->GetActor()) continue; StopDuel(client, duel); if (duel->GetTargetObject() == actor) { // Stop attacking if we are attacking the spawner. psserver->combatmanager->StopAttack(duel->GetActor()); } psserver->SendSystemInfo(client->GetClientNum(), "You yielded to %s", duel->GetName()); psserver->SendSystemInfo(duel->GetClientNum(), "%s yielded to you", client->GetName()); } // Stop attacking. psserver->combatmanager->StopAttack(actor); } void UserManager::HandleAttack(psUserCmdMessage& msg,Client *client,int clientnum) { Attack(msg.mode, client, clientnum); } void UserManager::Attack(int mode, Client *client,int clientnum) { if (!client->IsAlive() || client->IsFrozen()) { psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot fight now."); return; } gemObject *target = client->GetTargetObject(); if ( ! target ) { psserver->SendSystemError(clientnum,"You do not have a target selected."); return; } if ( target->IsAlive() == false ) { psserver->SendSystemError(clientnum,"%s is already dead.", (const char*)target->GetName() ); return; } if (client->IsAllowedToAttack(target)) psserver->combatmanager->AttackSomeone(client->GetActor(), target, mode ); } void UserManager::StopAttack(psUserCmdMessage& ,Client *client,int ) { // Stop attack if something is targeted. psserver->combatmanager->StopAttack(client->GetActor() ); } void UserManager::Assist( psUserCmdMessage& msg, Client* client, int clientnum ) { Client* targetClient = NULL; if (!client->IsAlive()) { psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot assist anybody now."); return; } // If the player doesn't provide an argument, use the players current // target instead. if ( msg.player == "" ) { int currentTarget = client->GetTargetClientID(); if ( currentTarget == -1 ) { psserver->SendSystemInfo( clientnum, "You have no target selected."); return; } if ( currentTarget == 0 ) { psserver->SendSystemInfo( clientnum, "You can assist other players only."); return; } targetClient = clients->Find( currentTarget ); if ( targetClient == NULL ) { psserver->SendSystemInfo( clientnum, "Internal error - client not found."); return; } } else { csString playerName = NormalizeCharacterName(msg.player); targetClient = clients->Find( playerName ); if ( !targetClient ) { psserver->SendSystemInfo( clientnum,"Specified player is not online." ); return; } } if ( targetClient == client ) { psserver->SendSystemInfo( clientnum,"You cannot assist yourself." ); return; } if ( !client->GetActor()->IsNear( targetClient->GetActor(), ASSIST_MAX_DIST ) ) { psserver->SendSystemInfo(clientnum, "Specified player is too far away." ); return; } gemObject* targetObject = targetClient->GetTargetObject(); if(!targetObject) { psserver->SendSystemInfo(clientnum, "Specified player has no target selected." ); return; } client->SetTargetObject( targetObject, true ); } void UserManager::UserStatRegeneration() { GEMSupervisor::GetSingleton().UpdateAllStats(); // Push a new event psUserStatRegeneration* event; nextUserStatRegeneration += 1000; event = new psUserStatRegeneration(this,nextUserStatRegeneration); psserver->GetEventManager()->Push(event); } void UserManager::Ready() { nextUserStatRegeneration = csGetTicks(); UserStatRegeneration(); } /** * Check target dead * Check target lootable by this client * Return lootable items list if present */ void UserManager::HandleLoot(Client *client) { int clientnum = client->GetClientNum(); if (!client->IsAlive()) { psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot loot now"); return; } gemObject *target = client->GetTargetObject(); if (!target) { psserver->SendSystemError(clientnum,"You don't have a target selected."); return; } if (target->IsAlive()) { psserver->SendSystemError(clientnum, "You can't loot person that is alive"); return; } // Check target lootable by this client gemNPC *npc = target->GetNPCPtr(); if (!npc) { psserver->SendSystemError(clientnum, "You can loot NPCs only"); return; } if (!npc->IsLootableClient(client->GetClientNum())) { Debug2(LOG_USER, client->GetClientNum(),"Client %d tried to loot mob that wasn't theirs.\n",client->GetClientNum() ); psserver->SendSystemError(client->GetClientNum(),"You are not allowed to loot %s.",target->GetName() ); return; } // Check to make sure loot is in range. if (client->GetActor()->RangeTo(target) > RANGE_TO_LOOT) { psserver->SendSystemError(client->GetClientNum(), "Too far away to loot %s", target->GetName() ); return; } // Return lootable items list if present psCharacter *chr = target->GetCharacterData(); if (chr) { // Send items to looting player psLootMessage loot; size_t count = chr->GetLootItems(loot, target->GetEntity()->GetID(), client->GetClientNum() ); if (count) { Debug3(LOG_LOOT, client->GetClientNum(),"Sending %u loot items to %s.\n",count,client->GetActor()->GetName() ); loot.SendMessage(); } else if(!count && !chr->GetLootMoney()) { Debug1(LOG_LOOT, client->GetClientNum(),"Mob doesn't have loot.\n"); psserver->SendSystemError(client->GetClientNum(),"%s has nothing to be looted.",target->GetName() ); } // Split up money among group int money = chr->GetLootMoney(); if (money) { Debug2(LOG_LOOT, client->GetClientNum(),"Splitting up %d money.\n", money); csRef group = client->GetActor()->GetGroup(); int remainder,each; if (group) { remainder = money %(int) group->GetMemberCount(); each = money /(int) group->GetMemberCount(); psMoney eachmoney = psMoney(each).Normalized(); psMoney remmoney = psMoney(remainder).Normalized(); csString remstr = remmoney.ToUserString(); csString eachstr = eachmoney.ToUserString(); if(each) { psSystemMessage loot(client->GetClientNum(),MSG_LOOT,"Everyone has looted %s.",eachstr.GetData() ); client->GetActor()->SendGroupMessage(loot.msg); npc->AdjustMoneyLootClients(eachmoney); } if(remainder) { psSystemMessage loot2(client->GetClientNum(),MSG_LOOT,"You have looted an extra %s.",remstr.GetData() ); loot2.SendMessage(); client->GetCharacterData()->AdjustMoney(remmoney); } } else { psMoney m = psMoney(money).Normalized(); csString mstr = m.ToUserString(); psSystemMessage loot(client->GetClientNum(),MSG_LOOT,"You have looted %s.",mstr.GetData() ); loot.SendMessage(); client->GetCharacterData()->AdjustMoney(m); } chr->AddLootMoney(-money); // zero out loot now } else Debug1(LOG_LOOT, client->GetClientNum(),"Mob has no money to loot.\n"); } } void UserManager::HandleQuests(Client *client) { psQuestListMessage quests; size_t count = client->GetActor()->GetCharacterData()->GetAssignedQuests(quests,client->GetClientNum() ); if (count) { Debug3(LOG_QUESTS, client->GetClientNum(),"Sending %u quests to player %s.\n",count,client->GetName() ); quests.SendMessage(); } else { Debug1(LOG_QUESTS, client->GetClientNum(),"Client has no quests yet.\n"); psserver->SendSystemError(client->GetClientNum(),"You have no active quests right now.",client->GetName() ); } } void UserManager::HandleTraining(Client *client) { if (!client->IsAlive()) { psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot train your skills now"); return; } // Check target is a Trainer gemObject *target = client->GetTargetObject(); if (!target || !target->GetActorPtr()) { psserver->SendSystemInfo(client->GetClientNum(), "No target selected for training!"); return; } // Check range if (client->GetActor()->RangeTo(target) > RANGE_TO_SELECT) { psserver->SendSystemInfo(client->GetClientNum(), "You are not in range to train with %s.",target->GetCharacterData()->GetCharName()); return; } if (!target->IsAlive()) { psserver->SendSystemInfo(client->GetClientNum(), "Can't train with a dead trainer!"); return; } psCharacter * trainer = target->GetCharacterData(); if (!trainer->IsTrainer()) { psserver->SendSystemInfo(client->GetClientNum(), "%s isn't a trainer.",target->GetCharacterData()->GetCharName()); return; } psserver->GetProgressionManager()->StartTraining(client,trainer); } void UserManager::GiveTip(int id) { unsigned int max=CacheManager::GetSingleton().GetTipLength(); unsigned int rnd = psserver->rng->Get(max); csString tip; CacheManager::GetSingleton().GetTipByID(rnd, tip); psserver->SendSystemInfo(id,tip.GetData()); } void UserManager::GiveMOTD(int id) { psserver->SendSystemInfo(id,psserver->GetMOTD()); } void UserManager::ChallengeToDuel(psUserCmdMessage& msg,Client *client) { int clientnum = client->GetClientNum(); if (!client->IsAlive()) { psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot challenge opponents now"); return; } // Check target dead gemObject *target = client->GetTargetObject(); if (!target) { psserver->SendSystemError(clientnum,"You don't have another player targeted."); return; } Client * targetClient = psserver->GetNetManager()->GetClient(target->GetClientID()); if (!targetClient) { psserver->SendSystemError(clientnum, "You can challenge other players only"); return; } // Distance if(client->GetActor()->RangeTo(target) > RANGE_TO_CHALLENGE) { psserver->SendSystemError(clientnum,target->GetName() + csString(" is too far away")); return; } if (!target->IsAlive()) { psserver->SendSystemError(clientnum, "You can't challenge a dead person"); return; } if (targetClient == client) { psserver->SendSystemError(clientnum, "You can't challenge yourself."); return; } // Check for pre-existing duel with this person if (client->IsDuelClient(target->GetClientID())) { psserver->SendSystemError(clientnum, "You have already agreed on this duel !"); return; } // Challenge csString question; question.Format("%s has challenged you to a duel! Click on Accept to allow the duel or Reject to ignore the challenge.", client->GetName() ); PendingDuelInvite *invite = new PendingDuelInvite(client, targetClient, question); psserver->questionmanager->SendQuestion(invite); } void UserManager::AcceptDuel(PendingDuelInvite *invite) { Client * inviteeClient = clients->Find(invite->clientnum); Client * inviterClient = clients->Find(invite->inviterClientNum); if (inviteeClient!=NULL && inviterClient!=NULL) { inviteeClient->AddDuelClient( invite->inviterClientNum ); inviterClient->AddDuelClient( invite->clientnum ); // Target eachother and update their GUIs inviteeClient->SetTargetObject(inviterClient->GetActor(),true); inviterClient->SetTargetObject(inviteeClient->GetActor(),true); } } void PendingDuelInvite::HandleAnswer(const csString & answer) { Client * client = psserver->GetConnections()->Find(clientnum); if (!client || client->IsDuelClient(inviterClientNum)) return; PendingInvite::HandleAnswer(answer); if (answer == "yes") psserver->usermanager->AcceptDuel(this); } void UserManager::StopDuel(Client *loser, Client *winner,bool award) { // Remove the duel from both sides. loser->RemoveDuelClient(winner->GetClientNum() ); loser->GetActor()->SetMode(PSCHARACTER_MODE_PEACE); winner->RemoveDuelClient(loser->GetClientNum()); winner->GetActor()->SetMode(PSCHARACTER_MODE_PEACE); if(award) { // Award duel points float delta = loser->GetCharacterData()->GetSkills()->GetBestSkillValue(true); if (delta) { float div = winner->GetCharacterData()->GetSkills()->GetBestSkillValue(true); delta /= (div)?div:1; } csString str; str.Format("%1.2f Duel points from %s awarded to %s.", delta,loser->GetName(),winner->GetName() ); Debug1(LOG_DUELS, loser->GetClientNum(),str); winner->GetCharacterData()->duel_points += delta; loser->GetCharacterData()->duel_points -= delta; /*psserver->SendSystemInfo(loser->GetClientNum(),str); psserver->SendSystemInfo(winner->GetClientNum(),str);*/ str.Format("You've lost %1.2f Duel Points!\nYou currently have %1.2f Duel Points.", delta, loser->GetCharacterData()->duel_points); psserver->SendSystemInfo(loser->GetClientNum(), str); str.Format("You've gained %1.2f Duel Points!\nYou currently have %1.2f Duel Points.", delta, winner->GetCharacterData()->duel_points); psserver->SendSystemInfo(winner->GetClientNum(), str); psserver->SendSystemOK(winner->GetClientNum(),"You've won the duel against %s!",loser->GetName()); psserver->SendSystemError(loser->GetClientNum(),"You've lost the duel against %s!",winner->GetName()); if (delta) { db->Command("update characters set duel_points=%1.2f where id=%d", winner->GetCharacterData()->duel_points, winner->GetCharacterData()->characterid); db->Command("update characters set duel_points=%1.2f where id=%d", loser->GetCharacterData()->duel_points, loser->GetCharacterData()->characterid); } } } void UserManager::YieldDuel(psUserCmdMessage& msg,Client *client) { int target = client->GetTargetClientID(); if (!target) { psserver->SendSystemError(client->GetClientNum(),"You don't have another player targeted."); return; } if (!client->IsDuelClient(target)) { psserver->SendSystemError(client->GetClientNum(),"You are not in a duel with your target."); return; } Client *targetClient = psserver->GetNetManager()->GetClient(target); if (targetClient == NULL) return; StopDuel(client,targetClient); } void UserManager::SwitchAttackTarget(Client *targeter, Client *targeted ) { // If we switch targets while in combat, start attacking the new // target, unless we no longer have a target. csString attackCmd; attackCmd.Format("/attack %i", targeter->GetCharacterData()->GetCombatStance()); psUserCmdMessage msg( attackCmd.GetData() ); if( targeted ) Attack( msg.mode, targeter, targeter->GetClientNum() ); else StopAttack( msg, targeter, targeter->GetClientNum() ); } /*---------------------------------------------------------------------*/ psUserStatRegeneration::psUserStatRegeneration(UserManager *mgr, csTicks ticks) : psGameEvent(ticks,0,"psUserStatRegeneration") { usermanager = mgr; } void psUserStatRegeneration::Trigger() { usermanager->UserStatRegeneration(); }