/* * client.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 #include "usermanager.h" #include "client.h" #include "psserver.h" #include "playergroup.h" #include "globals.h" #include "netmanager.h" #include "bulkobjects/pscharacter.h" #include "bulkobjects/psitem.h" #include "combatmanager.h" #include "util/psscf.h" #include "util/consoleout.h" #include "events.h" #include "advicemanager.h" #include "entitymanager.h" #include "util/psdatabase.h" #include "cachemanager.h" //#include "entitymanager.h" Client::Client () : accumulatedLag(0), ready(false),mute(false), accountID(0), playerID(0), securityLevel(0), superclient(false), lastResponse(-1), name(""), zombie(false), waypointEffectID(0) { actor = NULL; target = NULL; exchangeID = 0; isAdvisor = false; isFrozen = false; // pets[0] is a special case for the players familiar. pets.Insert( 0, (uint32)-1 ); lastInviteTime = 0; lastInviteResult = true; spamPoints = 0; hasBeenWarned = false; hasBeenPenalized = false; advisorPoints = 0; clientnum = 0; valid = false; } // Constructor for key search of bin tree Client::Client(LPSOCKADDR_IN addr) { Client::addr=*addr; valid=true; // necessary for key search of bin tree } Client::~Client() { } bool Client::Initialize(LPSOCKADDR_IN addr, uint32_t clientnum) { Client::addr=*addr; Client::clientnum=clientnum; Client::valid=true; outqueue = csPtr (new NetPacketQueueRefCount (MAXQUEUESIZE)); if (!outqueue) ERRORHALT("No Memory!"); return true; } bool Client::Disconnect() { // Make sure the advisor system knows this client is gone. if ( isAdvisor ) { psserver->GetAdviceManager()->RemoveAdvisor( this->GetClientNum(), 0); } if (GetActor() && GetActor()->InGroup()) { GetActor()->RemoveFromGroup(); } // Only save if an account has been found for this client. if (GetAccountID()) { SaveAccountData(); } return true; } bool Client::AllowDisconnect() { if(!GetActor() || !GetCharacterData()) return true; if(GetActor()->GetInvincibility()) return true; if(!zombie) { zombie = true; // max 3 minute timeout period zombietimeout = csGetTicks() + 3 * 60 * 1000; } else if(csGetTicks() > zombietimeout) return true; return !(GetActor()->GetMode() == PSCHARACTER_MODE_SPELL_CASTING || GetActor()->GetMode() == PSCHARACTER_MODE_COMBAT); } void Client::SetTargetObject(gemObject* newobject, bool updateClientGUI) { // We don't want to fire a target change event if the target hasn't changed. if (newobject == target) return; target = newobject; gemActor * myactor = GetActor(); if (myactor) { psTargetChangeEvent targetevent( myactor, newobject ); targetevent.FireEvent(); } if (updateClientGUI) { psGUITargetUpdateMessage updateMessage( GetClientNum(), newobject->GetEntity()->GetID() ); updateMessage.SendMessage(); } } void Client::SetFamiliar( gemActor *familiar ) { if ( familiar ) pets[0] = familiar->GetGemID(); else pets[0] = (uint32)-1; } gemActor* Client::GetFamiliar() { uint32 id; id = pets[ 0 ]; if ( id != (uint32)-1 ) { return GEMSupervisor::GetSingleton().FindNPCEntity( id ); } else return NULL; } void Client::AddPet( gemActor *pet ) { pets.Push( pet->GetGemID() ); } void Client::RemovePet( size_t index ) { pets[index] = (uint32)-1; } gemActor* Client::GetPet( size_t index ) { uint32 id; if ( index < pets.GetSize() ) { id = pets[ index ]; if ( id != -1 ) { return GEMSupervisor::GetSingleton().FindNPCEntity( id ); } else return NULL; } else return NULL; } size_t Client::GetNumPets() { return pets.GetSize(); } psCharacter *Client::GetCharacterData() { return (actor?actor->GetCharacterData():NULL); } bool Client::ValidateDistanceToTarget(float range) { // Check if target is set if (!target) return false; return actor->IsNear(target,range); } int Client::GetTargetClientID() { // Check if target is set if (!target) return -1; return target->GetClientID(); } int Client::GetGuildID() { psCharacter * mychar = GetCharacterData(); if (mychar == NULL) return 0; psGuildInfo * guild = mychar->GetGuild(); if (guild == NULL) return 0; return guild->id; } void Client::AddDuelClient(int clientnum) { if (!IsDuelClient(clientnum)) duel_clients.Push(clientnum); } void Client::RemoveDuelClient(int clientnum) { size_t i; for (i=0; iLength() < GetFloodMax()) ClearFlood(); return FloodMessagebuff->Get(row); } void Client::SetFloodStr(int row,csString& what,unsigned int ticks) { if (FloodMessagebuff->Length() < GetFloodMax()) ClearFlood(); FloodBuffRow newEnt(what,ticks); FloodMessagebuff->Put(row,newEnt); } void Client::ClearFlood() { FloodMessagebuff->DeleteAll(); //Fill with nothing FloodBuffRow empty("",0); for (size_t i =0;i < GetFloodMax();i++) { FloodMessagebuff->Put(i,empty); } } void Client::ShovelFlood(csString last) { if (FloodMessagebuff->Length() < GetFloodMax()) ClearFlood(); for (size_t z = 0; z < (GetFloodMax()-1); z++) { FloodBuffRow temp; temp.str = FloodMessagebuff->Get(z+1).str; temp.ticks = FloodMessagebuff->Get(z+1).ticks; FloodMessagebuff->Put(z,temp); } //Add new msg last in the array FloodBuffRow newMsg(last,csGetTicks()); FloodMessagebuff->DeleteIndex(GetFloodMax()-1); FloodMessagebuff->Put(GetFloodMax()-1,newMsg); } void Client::CheckBuffer() { for ( size_t z = GetFloodMax(); z-- > 0; ){ if ((csGetTicks() - GetFlood((int)z).ticks) >= FLOODFORGIVETIME) { //Time has elapsed, delete chat msg FloodBuffRow empty("",0); FloodMessagebuff->Put(z,empty); } } } FloodBuffRow::FloodBuffRow(csString newstr, unsigned int newticks) { str = newstr; ticks = newticks; } bool Client::IsAllowedToAttack(gemObject * target,bool inform) { csString tmp; const char *sMsg = NULL; if ( target == NULL ) { sMsg = "The target selected is no more valid."; if(inform) psserver->SendSystemError(clientnum, sMsg ); return false; } switch ( this->GetTargetType( target ) ) { case TARGET_NONE: sMsg = "You must select a target to attack."; break; case TARGET_NPC: sMsg = "%s is impervious to attack."; break; case TARGET_ITEM: sMsg = "You can't attack an inanimate object."; break; case TARGET_SELF: sMsg = "You cannot attack yourself."; break; case TARGET_FRIEND: sMsg = "You cannot attack %s."; break; case TARGET_GM: sMsg = "You cannot attack a GM."; break; case TARGET_FOE: /* Foe */ { bool canAttack = true; gemActor *foe = dynamic_cast(target); gemActor *me = GetActor(); if (foe == NULL || foe->GetDamageHistoryCount() == 0) break; csTicks lasttime = csGetTicks(); gemActor *lastAttacker=NULL; for (int i = (int)foe->GetDamageHistoryCount(); i>0; i--) { DamageHistory *lasthit = foe->GetDamageHistory(i-1); if (lasttime - lasthit->timestamp > 15000) break; // any 15 second gap is enough to make us stop looking else lasttime = lasthit->timestamp; if (!lasthit->attacker_ref.IsValid()) continue; // ignore disconnects lastAttacker = dynamic_cast((gemObject*) lasthit->attacker_ref); if (lastAttacker == NULL) continue; // shouldn't happen // If someone else hit first and I'm not grouped with them, I'm locked out if (lastAttacker != me && !me->IsGroupedWith(lastAttacker)) { canAttack = false; break; } } if (!canAttack) { if (lastAttacker && foe) tmp.Format("You must be grouped with %s to attack %s.", lastAttacker->GetName(), foe->GetName()); else tmp.Format("You are not allowed to attack right now."); sMsg = tmp.GetData(); } } break; case TARGET_PVP: /* Attackable player */ break; } if ( sMsg != NULL ) { if(inform) psserver->SendSystemError(clientnum, sMsg, target->GetName() ); return false; } return true; } int Client::GetTargetType(gemObject* target) { if (!target) { return TARGET_NONE; /* No Target */ } if (target->GetActorPtr() == NULL) { return TARGET_ITEM; /* Item */ } if (!target->IsAlive()) { return TARGET_DEAD; } if (GetActor() == target) { return TARGET_SELF; /* Self */ } if (target->GetCharacterData()->impervious_to_attack) { return TARGET_NPC; /* Impervious NPC */ } // Is target a NPC? Client* targetclient = psserver->GetNetManager()->GetAnyClient(target->GetClientID()); if (!targetclient) { if (target->GetCharacterData()->IsPet()) { /* Pet's target type depends on its owner's (enable when they can defend themselves) gemObject* owner = GEMSupervisor::GetSingleton().FindPlayerEntity( target->GetCharacterData()->GetOwnerID() ); if ( !owner || !IsAllowedToAttack(owner,false) ) */ return TARGET_FRIEND; } return TARGET_FOE; /* Foe */ } if (targetclient->GetActor()->GetInvincibility()) return TARGET_GM; /* Invincible GM */ // Challenged to a duel? if (IsDuelClient(target->GetClientID()) || targetclient->IsDuelClient(clientnum)) { return TARGET_PVP; /* Attackable player */ } // In PvP region? csVector3 attackerpos, targetpos; float yrot; iSector* attackersector, *targetsector; GetActor()->GetPosition(attackerpos, yrot, attackersector); target->GetPosition(targetpos, yrot, targetsector); if (psserver->GetCombatManager()->InPVPRegion(attackerpos,attackersector) && psserver->GetCombatManager()->InPVPRegion(targetpos,targetsector)) { return TARGET_PVP; /* Attackable player */ } // Declared war? psGuildInfo* attackguild = GetActor()->GetGuild(); psGuildInfo* targetguild = targetclient->GetActor()->GetGuild(); if (attackguild && targetguild && targetguild->IsGuildWarActive(attackguild)) { return TARGET_PVP; /* Attackable player */ } return TARGET_FRIEND; /* Friend */ } static inline void TestTarget(csString& targetDesc, int32_t targetType, enum TARGET_TYPES type, const char* desc) { if (targetType & type) { if (targetDesc.Length() > 0) { targetDesc.Append((targetType > (type * 2)) ? ", " : ", or "); } targetDesc.Append(desc); } } void Client::GetTargetTypeName(int32_t targetType, csString& targetDesc) const { targetDesc.Clear(); TestTarget(targetDesc, targetType, TARGET_NONE, "the surrounding area"); TestTarget(targetDesc, targetType, TARGET_NPC, "npcs"); TestTarget(targetDesc, targetType, TARGET_ITEM, "items"); TestTarget(targetDesc, targetType, TARGET_SELF, "yourself"); TestTarget(targetDesc, targetType, TARGET_FRIEND, "living friends"); TestTarget(targetDesc, targetType, TARGET_FOE, "living monsters"); TestTarget(targetDesc, targetType, TARGET_DEAD, "the dead"); TestTarget(targetDesc, targetType, TARGET_PVP, "living people"); } bool Client::IsAlive(void) const { return actor ? actor->IsAlive() : true; } void Client::SaveAccountData() { // First penalty after relogging should not be death if (spamPoints >= 2) spamPoints = 1; // Save to the db db->Command("UPDATE accounts SET spam_points = '%d', advisor_points = '%d' WHERE id = '%d' LIMIT 1", spamPoints, advisorPoints, accountID ); } uint32_t Client::WaypointGetEffectID() { if (waypointEffectID == 0) waypointEffectID = CacheManager::GetSingleton().NextEffectUID(); return waypointEffectID; }