mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
7609 lines
207 KiB
C++
7609 lines
207 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.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 except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
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 "../common/debug.h"
|
|
#include <iostream>
|
|
using namespace std;
|
|
#include <iomanip>
|
|
using namespace std;
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <math.h>
|
|
|
|
// for windows compile
|
|
#ifdef _WINDOWS
|
|
#define abs64 _abs64
|
|
#define snprintf _snprintf
|
|
#if (_MSC_VER < 1500)
|
|
#define vsnprintf _vsnprintf
|
|
#endif
|
|
#define strncasecmp _strnicmp
|
|
#define strcasecmp _stricmp
|
|
#else
|
|
#include <stdarg.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include "../common/unix.h"
|
|
#define abs64 abs
|
|
#endif
|
|
|
|
extern volatile bool RunLoops;
|
|
|
|
#include "../common/features.h"
|
|
#include "masterentity.h"
|
|
#include "worldserver.h"
|
|
#include "../common/misc.h"
|
|
#include "zonedb.h"
|
|
#include "../common/spdat.h"
|
|
#include "net.h"
|
|
#include "../common/packet_dump.h"
|
|
#include "../common/packet_functions.h"
|
|
#include "petitions.h"
|
|
#include "../common/serverinfo.h"
|
|
#include "../common/ZoneNumbers.h"
|
|
#include "../common/moremath.h"
|
|
#include "../common/guilds.h"
|
|
#include "../common/breakdowns.h"
|
|
#include "../common/rulesys.h"
|
|
#include "../common/MiscFunctions.h"
|
|
#include "forage.h"
|
|
#include "command.h"
|
|
#include "StringIDs.h"
|
|
#include "NpcAI.h"
|
|
#include "client_logs.h"
|
|
#include "guild_mgr.h"
|
|
#include "QuestParserCollection.h"
|
|
|
|
|
|
extern EntityList entity_list;
|
|
extern Zone* zone;
|
|
extern volatile bool ZoneLoaded;
|
|
extern WorldServer worldserver;
|
|
extern uint32 numclients;
|
|
extern PetitionList petition_list;
|
|
bool commandlogged;
|
|
char entirecommand[255];
|
|
extern DBAsyncFinishedQueue MTdbafq;
|
|
extern DBAsync *dbasync;
|
|
|
|
Client::Client(EQStreamInterface* ieqs)
|
|
: Mob("No name", // name
|
|
"", // lastname
|
|
0, // cur_hp
|
|
0, // max_hp
|
|
0, // gender
|
|
0, // race
|
|
0, // class
|
|
BT_Humanoid, // bodytype
|
|
0, // deity
|
|
0, // level
|
|
0, // npctypeid
|
|
0, // size
|
|
0.7, // runspeed
|
|
0, // heading
|
|
0, // x
|
|
0, // y
|
|
0, // z
|
|
0, // light
|
|
0xFF, // texture
|
|
0xFF, // helmtexture
|
|
0, // ac
|
|
0, // atk
|
|
0, // str
|
|
0, // sta
|
|
0, // dex
|
|
0, // agi
|
|
0, // int
|
|
0, // wis
|
|
0, // cha
|
|
0, // Luclin Hair Colour
|
|
0, // Luclin Beard Color
|
|
0, // Luclin Eye1
|
|
0, // Luclin Eye2
|
|
0, // Luclin Hair Style
|
|
0, // Luclin Face
|
|
0, // Luclin Beard
|
|
0, // Drakkin Heritage
|
|
0, // Drakkin Tattoo
|
|
0, // Drakkin Details
|
|
0, // Armor Tint
|
|
0xff, // AA Title
|
|
0, // see_invis
|
|
0, // see_invis_undead
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, // qglobal
|
|
0, // maxlevel
|
|
0 // scalerate
|
|
|
|
),
|
|
//these must be listed in the order they appear in client.h
|
|
position_timer(250),
|
|
hpupdate_timer(1800),
|
|
camp_timer(29000),
|
|
process_timer(100),
|
|
stamina_timer(40000),
|
|
zoneinpacket_timer(3000),
|
|
linkdead_timer(RuleI(Zone,ClientLinkdeadMS)),
|
|
dead_timer(2000),
|
|
global_channel_timer(1000),
|
|
shield_timer(500),
|
|
fishing_timer(8000),
|
|
endupkeep_timer(1000),
|
|
forget_timer(0),
|
|
autosave_timer(RuleI(Character, AutosaveIntervalS)*1000),
|
|
#ifdef REVERSE_AGGRO
|
|
scanarea_timer(AIClientScanarea_delay),
|
|
#endif
|
|
tribute_timer(Tribute_duration),
|
|
#ifdef PACKET_UPDATE_MANAGER
|
|
update_manager(ieqs),
|
|
#endif
|
|
proximity_timer(ClientProximity_interval),
|
|
TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000),
|
|
charm_update_timer(60000),
|
|
rest_timer(1),
|
|
charm_class_attacks_timer(3000),
|
|
charm_cast_timer(3500),
|
|
qglobal_purge_timer(30000),
|
|
TrackingTimer(2000),
|
|
RespawnFromHoverTimer(0),
|
|
merc_timer(RuleI(Mercs, UpkeepIntervalMS))
|
|
{
|
|
for(int cf=0; cf < _FilterCount; cf++)
|
|
ClientFilters[cf] = FilterShow;
|
|
character_id = 0;
|
|
conn_state = NoPacketsReceived;
|
|
client_data_loaded = false;
|
|
feigned = false;
|
|
berserk = false;
|
|
dead = false;
|
|
eqs = ieqs;
|
|
ip = eqs->GetRemoteIP();
|
|
port = ntohs(eqs->GetRemotePort());
|
|
client_state = CLIENT_CONNECTING;
|
|
Trader=false;
|
|
Buyer = false;
|
|
CustomerID = 0;
|
|
TrackingID = 0;
|
|
WID = 0;
|
|
account_id = 0;
|
|
admin = 0;
|
|
lsaccountid = 0;
|
|
shield_target = nullptr;
|
|
SQL_log = nullptr;
|
|
guild_id = GUILD_NONE;
|
|
guildrank = 0;
|
|
GuildBanker = false;
|
|
memset(lskey, 0, sizeof(lskey));
|
|
strcpy(account_name, "");
|
|
tellsoff = false;
|
|
last_reported_mana = 0;
|
|
last_reported_endur = 0;
|
|
gmhideme = false;
|
|
AFK = false;
|
|
LFG = false;
|
|
LFGFromLevel = 0;
|
|
LFGToLevel = 0;
|
|
LFGMatchFilter = false;
|
|
LFGComments[0] = '\0';
|
|
LFP = false;
|
|
gmspeed = 0;
|
|
playeraction = 0;
|
|
SetTarget(0);
|
|
auto_attack = false;
|
|
auto_fire = false;
|
|
linkdead_timer.Disable();
|
|
zonesummon_x = -2;
|
|
zonesummon_y = -2;
|
|
zonesummon_z = -2;
|
|
zonesummon_id = 0;
|
|
zonesummon_ignorerestrictions = 0;
|
|
zoning = false;
|
|
zone_mode = ZoneUnsolicited;
|
|
proximity_x = FLT_MAX; //arbitrary large number
|
|
proximity_y = FLT_MAX;
|
|
proximity_z = FLT_MAX;
|
|
casting_spell_id = 0;
|
|
npcflag = false;
|
|
npclevel = 0;
|
|
pQueuedSaveWorkID = 0;
|
|
position_timer_counter = 0;
|
|
fishing_timer.Disable();
|
|
shield_timer.Disable();
|
|
dead_timer.Disable();
|
|
camp_timer.Disable();
|
|
autosave_timer.Disable();
|
|
GetMercTimer()->Disable();
|
|
instalog = false;
|
|
pLastUpdate = 0;
|
|
pLastUpdateWZ = 0;
|
|
m_pp.autosplit = false;
|
|
// initialise haste variable
|
|
m_tradeskill_object = nullptr;
|
|
delaytimer = false;
|
|
PendingRezzXP = -1;
|
|
PendingRezzDBID = 0;
|
|
PendingRezzSpellID = 0;
|
|
numclients++;
|
|
// emuerror;
|
|
UpdateWindowTitle();
|
|
horseId = 0;
|
|
tgb = false;
|
|
tribute_master_id = 0xFFFFFFFF;
|
|
tribute_timer.Disable();
|
|
taskstate = nullptr;
|
|
TotalSecondsPlayed = 0;
|
|
keyring.clear();
|
|
bind_sight_target = nullptr;
|
|
mercid = 0;
|
|
mercSlot = 0;
|
|
InitializeMercInfo();
|
|
SetMerc(0);
|
|
|
|
logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED;
|
|
|
|
//for good measure:
|
|
memset(&m_pp, 0, sizeof(m_pp));
|
|
memset(&m_epp, 0, sizeof(m_epp));
|
|
PendingTranslocate = false;
|
|
PendingSacrifice = false;
|
|
BoatID = 0;
|
|
|
|
KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS));
|
|
GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS));
|
|
AttemptedMessages = 0;
|
|
TotalKarma = 0;
|
|
ClientVersion = EQClientUnknown;
|
|
ClientVersionBit = 0;
|
|
AggroCount = 0;
|
|
RestRegenHP = 0;
|
|
RestRegenMana = 0;
|
|
RestRegenEndurance = 0;
|
|
XPRate = 100;
|
|
cur_end = 0;
|
|
|
|
m_TimeSinceLastPositionCheck = 0;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
m_ShadowStepExemption = 0;
|
|
m_KnockBackExemption = 0;
|
|
m_PortExemption = 0;
|
|
m_SenseExemption = 0;
|
|
m_CheatDetectMoved = false;
|
|
CanUseReport = true;
|
|
aa_los_me.x = 0;
|
|
aa_los_me.y = 0;
|
|
aa_los_me.z = 0;
|
|
aa_los_them.x = 0;
|
|
aa_los_them.y = 0;
|
|
aa_los_them.z = 0;
|
|
aa_los_them_mob = nullptr;
|
|
los_status = false;
|
|
qGlobals = nullptr;
|
|
HideCorpseMode = HideCorpseNone;
|
|
PendingGuildInvitation = false;
|
|
|
|
cur_end = 0;
|
|
|
|
InitializeBuffSlots();
|
|
|
|
adventure_request_timer = nullptr;
|
|
adventure_create_timer = nullptr;
|
|
adventure_leave_timer = nullptr;
|
|
adventure_door_timer = nullptr;
|
|
adv_requested_data = nullptr;
|
|
adventure_stats_timer = nullptr;
|
|
adventure_leaderboard_timer = nullptr;
|
|
adv_data = nullptr;
|
|
adv_requested_theme = 0;
|
|
adv_requested_id = 0;
|
|
adv_requested_member_count = 0;
|
|
|
|
for(int i = 0; i < XTARGET_HARDCAP; ++i)
|
|
{
|
|
XTargets[i].Type = Auto;
|
|
XTargets[i].ID = 0;
|
|
XTargets[i].Name[0] = 0;
|
|
}
|
|
MaxXTargets = 5;
|
|
XTargetAutoAddHaters = true;
|
|
}
|
|
|
|
Client::~Client() {
|
|
#ifdef BOTS
|
|
Bot::ProcessBotOwnerRefDelete(this);
|
|
#endif
|
|
if(IsInAGuild())
|
|
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
|
|
|
|
Mob* horse = entity_list.GetMob(this->CastToClient()->GetHorseId());
|
|
if (horse)
|
|
horse->Depop();
|
|
|
|
Mob* merc = entity_list.GetMob(this->GetMercID());
|
|
if (merc)
|
|
merc->Depop();
|
|
|
|
if(Trader)
|
|
database.DeleteTraderItem(this->CharacterID());
|
|
|
|
if(Buyer)
|
|
ToggleBuyerMode(false);
|
|
|
|
if(conn_state != ClientConnectFinished) {
|
|
LogFile->write(EQEMuLog::Debug, "Client '%s' was destroyed before reaching the connected state:", GetName());
|
|
ReportConnectingState();
|
|
}
|
|
|
|
if(m_tradeskill_object != nullptr) {
|
|
m_tradeskill_object->Close();
|
|
m_tradeskill_object = nullptr;
|
|
}
|
|
|
|
#ifdef CLIENT_LOGS
|
|
client_logs.unsubscribeAll(this);
|
|
#endif
|
|
|
|
|
|
// if(AbilityTimer || GetLevel()>=51)
|
|
// database.UpdateAndDeleteAATimers(CharacterID());
|
|
|
|
ChangeSQLLog(nullptr);
|
|
if(IsDueling() && GetDuelTarget() != 0) {
|
|
Entity* entity = entity_list.GetID(GetDuelTarget());
|
|
if(entity != nullptr && entity->IsClient()) {
|
|
entity->CastToClient()->SetDueling(false);
|
|
entity->CastToClient()->SetDuelTarget(0);
|
|
entity_list.DuelMessage(entity->CastToClient(),this,true);
|
|
}
|
|
}
|
|
|
|
if (shield_target) {
|
|
for (int y = 0; y < 2; y++) {
|
|
if (shield_target->shielder[y].shielder_id == GetID()) {
|
|
shield_target->shielder[y].shielder_id = 0;
|
|
shield_target->shielder[y].shielder_bonus = 0;
|
|
}
|
|
}
|
|
shield_target = nullptr;
|
|
}
|
|
|
|
if(GetTarget())
|
|
GetTarget()->IsTargeted(-1);
|
|
|
|
//if we are in a group and we are not zoning, force leave the group
|
|
if(isgrouped && !zoning && ZoneLoaded)
|
|
LeaveGroup();
|
|
|
|
UpdateWho(2);
|
|
|
|
if(IsHoveringForRespawn())
|
|
{
|
|
m_pp.zone_id = m_pp.binds[0].zoneId;
|
|
m_pp.zoneInstance = 0;
|
|
x_pos = m_pp.binds[0].x;
|
|
y_pos = m_pp.binds[0].y;
|
|
z_pos = m_pp.binds[0].z;
|
|
}
|
|
|
|
// we save right now, because the client might be zoning and the world
|
|
// will need this data right away
|
|
Save(2); // This fails when database destructor is called first on shutdown
|
|
|
|
safe_delete(taskstate);
|
|
safe_delete(KarmaUpdateTimer);
|
|
safe_delete(GlobalChatLimiterTimer);
|
|
safe_delete(qGlobals);
|
|
safe_delete(adventure_request_timer);
|
|
safe_delete(adventure_create_timer);
|
|
safe_delete(adventure_leave_timer);
|
|
safe_delete(adventure_door_timer);
|
|
safe_delete(adventure_stats_timer);
|
|
safe_delete(adventure_leaderboard_timer);
|
|
safe_delete_array(adv_requested_data);
|
|
safe_delete_array(adv_data);
|
|
|
|
numclients--;
|
|
UpdateWindowTitle();
|
|
if(zone)
|
|
zone->RemoveAuth(GetName());
|
|
|
|
//let the stream factory know were done with this stream
|
|
eqs->Close();
|
|
eqs->ReleaseFromUse();
|
|
|
|
entity_list.RemoveClient(this);
|
|
UninitializeBuffSlots();
|
|
}
|
|
|
|
void Client::SendLogoutPackets() {
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_CancelTrade, sizeof(CancelTrade_Struct));
|
|
CancelTrade_Struct* ct = (CancelTrade_Struct*) outapp->pBuffer;
|
|
ct->fromid = GetID();
|
|
ct->action = groupActUpdate;
|
|
FastQueuePacket(&outapp);
|
|
|
|
outapp = new EQApplicationPacket(OP_PreLogoutReply);
|
|
FastQueuePacket(&outapp);
|
|
|
|
}
|
|
|
|
void Client::ReportConnectingState() {
|
|
switch(conn_state) {
|
|
case NoPacketsReceived: //havent gotten anything
|
|
LogFile->write(EQEMuLog::Debug, "Client has not sent us an initial zone entry packet.");
|
|
break;
|
|
case ReceivedZoneEntry: //got the first packet, loading up PP
|
|
LogFile->write(EQEMuLog::Debug, "Client sent initial zone packet, but we never got their player info from the database.");
|
|
break;
|
|
case PlayerProfileLoaded: //our DB work is done, sending it
|
|
LogFile->write(EQEMuLog::Debug, "We were sending the player profile, tributes, tasks, spawns, time and weather, but never finished.");
|
|
break;
|
|
case ZoneInfoSent: //includes PP, tributes, tasks, spawns, time and weather
|
|
LogFile->write(EQEMuLog::Debug, "We successfully sent player info and spawns, waiting for client to request new zone.");
|
|
break;
|
|
case NewZoneRequested: //received and sent new zone request
|
|
LogFile->write(EQEMuLog::Debug, "We received client's new zone request, waiting for client spawn request.");
|
|
break;
|
|
case ClientSpawnRequested: //client sent ReqClientSpawn
|
|
LogFile->write(EQEMuLog::Debug, "We received the client spawn request, and were sending objects, doors, zone points and some other stuff, but never finished.");
|
|
break;
|
|
case ZoneContentsSent: //objects, doors, zone points
|
|
LogFile->write(EQEMuLog::Debug, "The rest of the zone contents were successfully sent, waiting for client ready notification.");
|
|
break;
|
|
case ClientReadyReceived: //client told us its ready, send them a bunch of crap like guild MOTD, etc
|
|
LogFile->write(EQEMuLog::Debug, "We received client ready notification, but never finished Client::CompleteConnect");
|
|
break;
|
|
case ClientConnectFinished: //client finally moved to finished state, were done here
|
|
LogFile->write(EQEMuLog::Debug, " Client is successfully connected.");
|
|
break;
|
|
};
|
|
}
|
|
|
|
bool Client::Save(uint8 iCommitNow) {
|
|
#if 0
|
|
// Orig. Offset: 344 / 0x00000000
|
|
// Length: 36 / 0x00000024
|
|
unsigned char rawData[36] =
|
|
{
|
|
0x0D, 0x30, 0xE1, 0x30, 0x1E, 0x10, 0x22, 0x10, 0x20, 0x10, 0x21, 0x10, 0x1C, 0x20, 0x1F, 0x10,
|
|
0x7C, 0x10, 0x68, 0x10, 0x51, 0x10, 0x78, 0x10, 0xBD, 0x10, 0xD2, 0x10, 0xCD, 0x10, 0xD1, 0x10,
|
|
0x01, 0x10, 0x6D, 0x10
|
|
} ;
|
|
for (int tmp = 0;tmp <=35;tmp++){
|
|
m_pp.unknown0256[89+tmp] = rawData[tmp];
|
|
}
|
|
#endif
|
|
|
|
if(!ClientDataLoaded())
|
|
return false;
|
|
_ZP(Client_Save);
|
|
|
|
m_pp.x = x_pos;
|
|
m_pp.y = y_pos;
|
|
m_pp.z = z_pos;
|
|
m_pp.guildrank=guildrank;
|
|
m_pp.heading = heading;
|
|
|
|
// Temp Hack for signed values until we get the root of the problem changed over to signed...
|
|
if (m_pp.copper < 0) { m_pp.copper = 0; }
|
|
if (m_pp.silver < 0) { m_pp.silver = 0; }
|
|
if (m_pp.gold < 0) { m_pp.gold = 0; }
|
|
if (m_pp.platinum < 0) { m_pp.platinum = 0; }
|
|
if (m_pp.copper_bank < 0) { m_pp.copper_bank = 0; }
|
|
if (m_pp.silver_bank < 0) { m_pp.silver_bank = 0; }
|
|
if (m_pp.gold_bank < 0) { m_pp.gold_bank = 0; }
|
|
if (m_pp.platinum_bank < 0) { m_pp.platinum_bank = 0; }
|
|
|
|
|
|
int spentpoints=0;
|
|
for(int a=0;a < MAX_PP_AA_ARRAY;a++) {
|
|
uint32 points = aa[a]->value;
|
|
if(points > HIGHEST_AA_VALUE) // Unifying this
|
|
{
|
|
aa[a]->value = HIGHEST_AA_VALUE;
|
|
points = HIGHEST_AA_VALUE;
|
|
}
|
|
if (points > 0)
|
|
{
|
|
SendAA_Struct* curAA = zone->FindAA(aa[a]->AA-aa[a]->value+1);
|
|
if(curAA)
|
|
{
|
|
for (int rank=0; rank<points; rank++)
|
|
{
|
|
std::map<uint32, AALevelCost_Struct>::iterator RequiredLevel = AARequiredLevelAndCost.find(aa[a]->AA-aa[a]->value + 1 + rank);
|
|
|
|
if(RequiredLevel != AARequiredLevelAndCost.end())
|
|
{
|
|
spentpoints += RequiredLevel->second.Cost;
|
|
}
|
|
else
|
|
spentpoints += (curAA->cost + (curAA->cost_inc * rank));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pp.aapoints_spent = spentpoints + m_epp.expended_aa;
|
|
|
|
if (GetHP() <= 0) {
|
|
m_pp.cur_hp = GetMaxHP();
|
|
}
|
|
else
|
|
m_pp.cur_hp = GetHP();
|
|
|
|
m_pp.mana = cur_mana;
|
|
m_pp.endurance = cur_end;
|
|
|
|
database.SaveBuffs(this);
|
|
|
|
TotalSecondsPlayed += (time(nullptr) - m_pp.lastlogin);
|
|
m_pp.timePlayedMin = (TotalSecondsPlayed / 60);
|
|
m_pp.RestTimer = rest_timer.GetRemainingTime() / 1000;
|
|
|
|
if(GetMercInfo().MercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS))
|
|
GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS);
|
|
|
|
if(GetMercTimer()->Enabled()) {
|
|
GetMercInfo().MercTimerRemaining = GetMercTimer()->GetRemainingTime();
|
|
}
|
|
|
|
if (GetMerc() && !dead) {
|
|
|
|
} else {
|
|
memset(&m_mercinfo, 0, sizeof(struct MercInfo));
|
|
}
|
|
|
|
m_pp.lastlogin = time(nullptr);
|
|
if (pQueuedSaveWorkID) {
|
|
dbasync->CancelWork(pQueuedSaveWorkID);
|
|
pQueuedSaveWorkID = 0;
|
|
}
|
|
|
|
if (GetPet() && !GetPet()->IsFamiliar() && GetPet()->CastToNPC()->GetPetSpellID() && !dead) {
|
|
NPC *pet = GetPet()->CastToNPC();
|
|
m_petinfo.SpellID = pet->CastToNPC()->GetPetSpellID();
|
|
m_petinfo.HP = pet->GetHP();
|
|
m_petinfo.Mana = pet->GetMana();
|
|
pet->GetPetState(m_petinfo.Buffs, m_petinfo.Items, m_petinfo.Name);
|
|
m_petinfo.petpower = pet->GetPetPower();
|
|
} else {
|
|
memset(&m_petinfo, 0, sizeof(struct PetInfo));
|
|
}
|
|
database.SavePetInfo(this);
|
|
|
|
if(tribute_timer.Enabled()) {
|
|
m_pp.tribute_time_remaining = tribute_timer.GetRemainingTime();
|
|
} else {
|
|
m_pp.tribute_time_remaining = 0xFFFFFFFF;
|
|
m_pp.tribute_active = 0;
|
|
}
|
|
|
|
p_timers.Store(&database);
|
|
|
|
// printf("Dumping inventory on save:\n");
|
|
// m_inv.dumpInventory();
|
|
|
|
SaveTaskState();
|
|
if (iCommitNow <= 1) {
|
|
char* query = 0;
|
|
uint32_breakdown workpt;
|
|
workpt.b4() = DBA_b4_Entity;
|
|
workpt.w2_3() = GetID();
|
|
workpt.b1() = DBA_b1_Entity_Client_Save;
|
|
DBAsyncWork* dbaw = new DBAsyncWork(&database, &MTdbafq, workpt, DBAsync::Write, 0xFFFFFFFF);
|
|
dbaw->AddQuery(iCommitNow == 0 ? true : false, &query, database.SetPlayerProfile_MQ(&query, account_id, character_id, &m_pp, &m_inv, &m_epp, 0, 0, MaxXTargets), false);
|
|
if (iCommitNow == 0){
|
|
pQueuedSaveWorkID = dbasync->AddWork(&dbaw, 2500);
|
|
}
|
|
else {
|
|
dbasync->AddWork(&dbaw, 0);
|
|
SaveBackup();
|
|
}
|
|
safe_delete_array(query);
|
|
return true;
|
|
}
|
|
else if (database.SetPlayerProfile(account_id, character_id, &m_pp, &m_inv, &m_epp, 0, 0, MaxXTargets)) {
|
|
SaveBackup();
|
|
}
|
|
else {
|
|
cerr << "Failed to update player profile" << endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Client::SaveBackup() {
|
|
if (!RunLoops)
|
|
return;
|
|
char* query = 0;
|
|
DBAsyncWork* dbaw = new DBAsyncWork(&database, &DBAsyncCB_CharacterBackup, this->CharacterID(), DBAsync::Read);
|
|
dbaw->AddQuery(0, &query, MakeAnyLenString(&query, "Select id, UNIX_TIMESTAMP()-UNIX_TIMESTAMP(ts) as age from character_backup where charid=%u and backupreason=0 order by ts asc", this->CharacterID()), true);
|
|
dbasync->AddWork(&dbaw, 0);
|
|
}
|
|
|
|
CLIENTPACKET::CLIENTPACKET()
|
|
{
|
|
app = nullptr;
|
|
ack_req = false;
|
|
}
|
|
|
|
CLIENTPACKET::~CLIENTPACKET()
|
|
{
|
|
safe_delete(app);
|
|
}
|
|
|
|
//this assumes we do not own pApp, and clones it.
|
|
bool Client::AddPacket(const EQApplicationPacket *pApp, bool bAckreq) {
|
|
if (!pApp)
|
|
return false;
|
|
if(!zoneinpacket_timer.Enabled()) {
|
|
//drop the packet because it will never get sent.
|
|
return(false);
|
|
}
|
|
CLIENTPACKET *c = new CLIENTPACKET;
|
|
|
|
c->ack_req = bAckreq;
|
|
c->app = pApp->Copy();
|
|
|
|
clientpackets.Append(c);
|
|
return true;
|
|
}
|
|
|
|
//this assumes that it owns the object pointed to by *pApp
|
|
bool Client::AddPacket(EQApplicationPacket** pApp, bool bAckreq) {
|
|
if (!pApp || !(*pApp))
|
|
return false;
|
|
if(!zoneinpacket_timer.Enabled()) {
|
|
//drop the packet because it will never get sent.
|
|
return(false);
|
|
}
|
|
CLIENTPACKET *c = new CLIENTPACKET;
|
|
|
|
c->ack_req = bAckreq;
|
|
c->app = *pApp;
|
|
*pApp = 0;
|
|
|
|
clientpackets.Append(c);
|
|
return true;
|
|
}
|
|
|
|
bool Client::SendAllPackets() {
|
|
LinkedListIterator<CLIENTPACKET*> iterator(clientpackets);
|
|
|
|
CLIENTPACKET* cp = 0;
|
|
iterator.Reset();
|
|
while(iterator.MoreElements()) {
|
|
cp = iterator.GetData();
|
|
if(eqs)
|
|
eqs->FastQueuePacket((EQApplicationPacket **)&cp->app, cp->ack_req);
|
|
iterator.RemoveCurrent();
|
|
#if EQDEBUG >= 6
|
|
LogFile->write(EQEMuLog::Normal, "Transmitting a packet");
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CONN_STATUS required_state, eqFilterType filter) {
|
|
/* if (app->opcode==0x9999) {
|
|
cout << "Sending an unknown opcode from: " << endl;
|
|
print_stacktrace();
|
|
}
|
|
if (app->opcode==OP_SkillUpdate) {
|
|
cout << "Sending OP_SkillUpdate from: " << endl;
|
|
print_stacktrace();
|
|
}
|
|
*/
|
|
_ZP(Client_QueuePacket);
|
|
if(filter!=FilterNone){
|
|
//this is incomplete... no support for FilterShowGroupOnly or FilterShowSelfOnly
|
|
if(GetFilter(filter) == FilterHide)
|
|
return; //Client has this filter on, no need to send packet
|
|
}
|
|
if(client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED){
|
|
AddPacket(app, ack_req);
|
|
return;
|
|
}
|
|
|
|
// if the program doesnt care about the status or if the status isnt what we requested
|
|
if (required_state != CLIENT_CONNECTINGALL && client_state != required_state)
|
|
{
|
|
// todo: save packets for later use
|
|
AddPacket(app, ack_req);
|
|
// LogFile->write(EQEMuLog::Normal, "Adding Packet to list (%d) (%d)", app->GetOpcode(), (int)required_state);
|
|
}
|
|
else
|
|
if(eqs)
|
|
eqs->QueuePacket(app, ack_req);
|
|
}
|
|
|
|
void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) {
|
|
|
|
//cout << "Sending: 0x" << hex << setw(4) << setfill('0') << (*app)->GetOpcode() << dec << ", size=" << (*app)->size << endl;
|
|
|
|
// if the program doesnt care about the status or if the status isnt what we requested
|
|
if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) {
|
|
// todo: save packets for later use
|
|
AddPacket(app, ack_req);
|
|
// LogFile->write(EQEMuLog::Normal, "Adding Packet to list (%d) (%d)", (*app)->GetOpcode(), (int)required_state);
|
|
return;
|
|
}
|
|
else {
|
|
if(eqs)
|
|
eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req);
|
|
else if (app && (*app))
|
|
delete *app;
|
|
*app = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_skill, const char* orig_message, const char* targetname) {
|
|
char message[4096];
|
|
strn0cpy(message, orig_message, sizeof(message));
|
|
|
|
|
|
#if EQDEBUG >= 11
|
|
LogFile->write(EQEMuLog::Debug,"Client::ChannelMessageReceived() Channel:%i message:'%s'", chan_num, message);
|
|
#endif
|
|
|
|
if (targetname == nullptr) {
|
|
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
|
|
}
|
|
|
|
if(RuleB(Chat, EnableAntiSpam))
|
|
{
|
|
if(strcmp(targetname, "discard") != 0)
|
|
{
|
|
if(chan_num == 3 || chan_num == 4 || chan_num == 5 || chan_num == 7)
|
|
{
|
|
if(GlobalChatLimiterTimer)
|
|
{
|
|
if(GlobalChatLimiterTimer->Check(false))
|
|
{
|
|
GlobalChatLimiterTimer->Start(RuleI(Chat, IntervalDurationMS));
|
|
AttemptedMessages = 0;
|
|
}
|
|
}
|
|
|
|
uint32 AllowedMessages = RuleI(Chat, MinimumMessagesPerInterval) + TotalKarma;
|
|
AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages;
|
|
|
|
if(RuleI(Chat, MinStatusToBypassAntiSpam) <= Admin())
|
|
AllowedMessages = 10000;
|
|
|
|
AttemptedMessages++;
|
|
if(AttemptedMessages > AllowedMessages)
|
|
{
|
|
if(AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick))
|
|
{
|
|
Kick();
|
|
return;
|
|
}
|
|
if(GlobalChatLimiterTimer)
|
|
{
|
|
Message(0, "You have been rate limited, you can send more messages in %i seconds.",
|
|
GlobalChatLimiterTimer->GetRemainingTime() / 1000);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Message(0, "You have been rate limited, you can send more messages in 60 seconds.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(RuleB(QueryServ, PlayerChatLogging)) {
|
|
ServerPacket* pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1);
|
|
Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer;
|
|
|
|
if(chan_num == 0)
|
|
sem->guilddbid = GuildID();
|
|
else
|
|
sem->guilddbid = 0;
|
|
|
|
strcpy(sem->message, message);
|
|
sem->minstatus = this->Admin();
|
|
sem->type = chan_num;
|
|
if(targetname != 0)
|
|
strcpy(sem->to, targetname);
|
|
|
|
if(GetName() != 0)
|
|
strcpy(sem->from, GetName());
|
|
|
|
pack->Deflate();
|
|
if(worldserver.Connected())
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
|
|
// Garble the message based on drunkness
|
|
if (m_pp.intoxication > 0) {
|
|
GarbleMessage(message, (int)(m_pp.intoxication / 3));
|
|
language = 0; // No need for language when drunk
|
|
}
|
|
|
|
switch(chan_num)
|
|
{
|
|
case 0: { // GuildChat
|
|
if (!IsInAGuild())
|
|
Message_StringID(MT_DefaultText, GUILD_NOT_MEMBER2); //You are not a member of any guild.
|
|
else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_SPEAK))
|
|
Message(0, "Error: You dont have permission to speak to the guild.");
|
|
else if (!worldserver.SendChannelMessage(this, targetname, chan_num, GuildID(), language, message))
|
|
Message(0, "Error: World server disconnected");
|
|
break;
|
|
}
|
|
case 2: { // GroupChat
|
|
Raid* raid = entity_list.GetRaidByClient(this);
|
|
if(raid) {
|
|
raid->RaidGroupSay((const char*) message, this);
|
|
break;
|
|
}
|
|
|
|
Group* group = GetGroup();
|
|
if(group != nullptr) {
|
|
group->GroupMessage(this,language,lang_skill,(const char*) message);
|
|
}
|
|
break;
|
|
}
|
|
case 15: { //raid say
|
|
Raid* raid = entity_list.GetRaidByClient(this);
|
|
if(raid){
|
|
raid->RaidSay((const char*) message, this);
|
|
}
|
|
break;
|
|
}
|
|
case 3: { // Shout
|
|
Mob *sender = this;
|
|
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
|
|
sender = GetPet();
|
|
|
|
entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message);
|
|
break;
|
|
}
|
|
case 4: { // Auction
|
|
if(RuleB(Chat, ServerWideAuction))
|
|
{
|
|
if(!global_channel_timer.Check())
|
|
{
|
|
if(strlen(targetname) == 0)
|
|
ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer??
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(GetRevoked())
|
|
{
|
|
Message(0, "You have been revoked. You may not talk on Auction.");
|
|
return;
|
|
}
|
|
|
|
if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit))
|
|
{
|
|
if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit))
|
|
{
|
|
Message(0, "You do not have permission to talk in Auction at this time.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!worldserver.SendChannelMessage(this, 0, 4, 0, language, message))
|
|
Message(0, "Error: World server disconnected");
|
|
}
|
|
else if(!RuleB(Chat, ServerWideAuction)) {
|
|
Mob *sender = this;
|
|
|
|
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
|
|
sender = GetPet();
|
|
|
|
entity_list.ChannelMessage(sender, chan_num, language, message);
|
|
}
|
|
break;
|
|
}
|
|
case 5: { // OOC
|
|
if(RuleB(Chat, ServerWideOOC))
|
|
{
|
|
if(!global_channel_timer.Check())
|
|
{
|
|
if(strlen(targetname) == 0)
|
|
ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer??
|
|
else
|
|
return;
|
|
}
|
|
if(worldserver.IsOOCMuted() && admin < 100)
|
|
{
|
|
Message(0,"OOC has been muted. Try again later.");
|
|
return;
|
|
}
|
|
|
|
if(GetRevoked())
|
|
{
|
|
Message(0, "You have been revoked. You may not talk on OOC.");
|
|
return;
|
|
}
|
|
|
|
if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit))
|
|
{
|
|
if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit))
|
|
{
|
|
Message(0, "You do not have permission to talk in OOC at this time.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!worldserver.SendChannelMessage(this, 0, 5, 0, language, message))
|
|
{
|
|
Message(0, "Error: World server disconnected");
|
|
}
|
|
}
|
|
else if(!RuleB(Chat, ServerWideOOC))
|
|
{
|
|
Mob *sender = this;
|
|
|
|
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
|
|
sender = GetPet();
|
|
|
|
entity_list.ChannelMessage(sender, chan_num, language, message);
|
|
}
|
|
break;
|
|
}
|
|
case 6: // Broadcast
|
|
case 11: { // GMSay
|
|
if (!(admin >= 80))
|
|
Message(0, "Error: Only GMs can use this channel");
|
|
else if (!worldserver.SendChannelMessage(this, targetname, chan_num, 0, language, message))
|
|
Message(0, "Error: World server disconnected");
|
|
break;
|
|
}
|
|
case 7: { // Tell
|
|
if(!global_channel_timer.Check())
|
|
{
|
|
if(strlen(targetname) == 0)
|
|
ChannelMessageReceived(7, language, lang_skill, message, "discard"); //Fast typer or spammer??
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(GetRevoked())
|
|
{
|
|
Message(0, "You have been revoked. You may not send tells.");
|
|
return;
|
|
}
|
|
|
|
if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit))
|
|
{
|
|
if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit))
|
|
{
|
|
Message(0, "You do not have permission to send tells at this time.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
char target_name[64];
|
|
|
|
if(targetname)
|
|
{
|
|
size_t i = strlen(targetname);
|
|
int x;
|
|
for(x = 0; x < i; ++x)
|
|
{
|
|
if(targetname[x] == '%')
|
|
{
|
|
target_name[x] = '/';
|
|
}
|
|
else
|
|
{
|
|
target_name[x] = targetname[x];
|
|
}
|
|
}
|
|
target_name[x] = '\0';
|
|
}
|
|
|
|
if(!worldserver.SendChannelMessage(this, target_name, chan_num, 0, language, message))
|
|
Message(0, "Error: World server disconnected");
|
|
break;
|
|
}
|
|
case 8: { // /say
|
|
if(message[0] == COMMAND_CHAR) {
|
|
if(command_dispatch(this, message) == -2) {
|
|
if(RuleB(Chat, FlowCommandstoPerl_EVENT_SAY)) {
|
|
if(parse->PlayerHasQuestSub("EVENT_SAY")) {
|
|
parse->EventPlayer(EVENT_SAY, this, message, language);
|
|
}
|
|
} else {
|
|
this->Message(13, "Command '%s' not recognized.", message);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
Mob* sender = this;
|
|
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
|
|
sender = GetPet();
|
|
|
|
printf("Message: %s\n",message);
|
|
entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message);
|
|
if(parse->PlayerHasQuestSub("EVENT_SAY"))
|
|
{
|
|
parse->EventPlayer(EVENT_SAY, this, message, language);
|
|
}
|
|
|
|
if (sender != this)
|
|
break;
|
|
|
|
if(quest_manager.ProximitySayInUse())
|
|
entity_list.ProcessProximitySay(message, this, language);
|
|
|
|
if (GetTarget() != 0 && GetTarget()->IsNPC()) {
|
|
if(!GetTarget()->CastToNPC()->IsEngaged()) {
|
|
CheckLDoNHail(GetTarget());
|
|
CheckEmoteHail(GetTarget(), message);
|
|
|
|
if(parse->HasQuestSub(GetTarget()->GetNPCTypeID(), "EVENT_SAY")){
|
|
if (DistNoRootNoZ(*GetTarget()) <= 200) {
|
|
if(GetTarget()->CastToNPC()->IsMoving() && !GetTarget()->CastToNPC()->IsOnHatelist(GetTarget()))
|
|
GetTarget()->CastToNPC()->PauseWandering(RuleI(NPC, SayPauseTimeInSec));
|
|
parse->EventNPC(EVENT_SAY, GetTarget()->CastToNPC(), this, message, language);
|
|
}
|
|
}
|
|
|
|
if (RuleB(TaskSystem, EnableTaskSystem) && DistNoRootNoZ(*GetTarget()) <= 200) {
|
|
|
|
if(GetTarget()->CastToNPC()->IsMoving() && !GetTarget()->CastToNPC()->IsOnHatelist(GetTarget()))
|
|
GetTarget()->CastToNPC()->PauseWandering(RuleI(NPC, SayPauseTimeInSec));
|
|
|
|
if(UpdateTasksOnSpeakWith(GetTarget()->GetNPCTypeID())) {
|
|
// If the client had an activity to talk to this NPC, make the NPC turn to face him if
|
|
// he isn't moving. Makes things look better.
|
|
if(!GetTarget()->CastToNPC()->IsMoving())
|
|
GetTarget()->FaceTarget(this);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(parse->HasQuestSub(GetTarget()->GetNPCTypeID(), "EVENT_AGGRO_SAY")) {
|
|
if (DistNoRootNoZ(*GetTarget()) <= 200) {
|
|
parse->EventNPC(EVENT_AGGRO_SAY, GetTarget()->CastToNPC(), this, message, language);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
break;
|
|
}
|
|
case 20:
|
|
{
|
|
// UCS Relay for Underfoot and later.
|
|
if(!worldserver.SendChannelMessage(this, 0, chan_num, 0, language, message))
|
|
Message(0, "Error: World server disconnected");
|
|
break;
|
|
}
|
|
case 22:
|
|
{
|
|
// Emotes for Underfoot and later.
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Emote, 4 + strlen(message) + strlen(GetName()) + 2);
|
|
Emote_Struct* es = (Emote_Struct*)outapp->pBuffer;
|
|
char *Buffer = (char *)es;
|
|
Buffer += 4;
|
|
snprintf(Buffer, sizeof(Emote_Struct) - 4, "%s %s", GetName(), message);
|
|
entity_list.QueueCloseClients(this, outapp, true, 100, 0, true, FilterSocials);
|
|
safe_delete(outapp);
|
|
break;
|
|
}
|
|
default: {
|
|
Message(0, "Channel (%i) not implemented", (uint16)chan_num);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no language skill is specified, call the function with a skill of 100.
|
|
void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, const char* message, ...) {
|
|
ChannelMessageSend(from, to, chan_num, language, 100, message);
|
|
}
|
|
|
|
void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...) {
|
|
if ((chan_num==11 && !(this->GetGM())) || (chan_num==10 && this->Admin()<80)) // dont need to send /pr & /petition to everybody
|
|
return;
|
|
va_list argptr;
|
|
char buffer[4096];
|
|
char message_sender[64];
|
|
|
|
va_start(argptr, message);
|
|
vsnprintf(buffer, 4096, message, argptr);
|
|
va_end(argptr);
|
|
|
|
EQApplicationPacket app(OP_ChannelMessage, sizeof(ChannelMessage_Struct)+strlen(buffer)+1);
|
|
ChannelMessage_Struct* cm = (ChannelMessage_Struct*)app.pBuffer;
|
|
|
|
if (from == 0)
|
|
strcpy(cm->sender, "ZServer");
|
|
else if (from[0] == 0)
|
|
strcpy(cm->sender, "ZServer");
|
|
else {
|
|
CleanMobName(from, message_sender);
|
|
strcpy(cm->sender, message_sender);
|
|
}
|
|
if (to != 0)
|
|
strcpy((char *) cm->targetname, to);
|
|
else if (chan_num == 7)
|
|
strcpy(cm->targetname, m_pp.name);
|
|
else
|
|
cm->targetname[0] = 0;
|
|
|
|
uint8 ListenerSkill;
|
|
|
|
if (language < MAX_PP_LANGUAGE) {
|
|
ListenerSkill = m_pp.languages[language];
|
|
if (ListenerSkill == 0) {
|
|
cm->language = (MAX_PP_LANGUAGE - 1); // in an unknown tongue
|
|
}
|
|
else {
|
|
cm->language = language;
|
|
}
|
|
}
|
|
else {
|
|
ListenerSkill = m_pp.languages[0];
|
|
cm->language = 0;
|
|
}
|
|
|
|
// set effective language skill = lower of sender and receiver skills
|
|
int32 EffSkill = (lang_skill < ListenerSkill ? lang_skill : ListenerSkill);
|
|
if (EffSkill > 100) // maximum language skill is 100
|
|
EffSkill = 100;
|
|
cm->skill_in_language = EffSkill;
|
|
|
|
// Garble the message based on listener skill
|
|
if (ListenerSkill < 100) {
|
|
GarbleMessage(buffer, (100 - ListenerSkill));
|
|
}
|
|
|
|
cm->chan_num = chan_num;
|
|
strcpy(&cm->message[0], buffer);
|
|
QueuePacket(&app);
|
|
|
|
if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up
|
|
if ((m_pp.languages[language] <= lang_skill) && (from != this->GetName()))
|
|
CheckLanguageSkillIncrease(language, lang_skill);
|
|
}
|
|
}
|
|
|
|
void Client::Message(uint32 type, const char* message, ...) {
|
|
if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee)
|
|
return;
|
|
if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self...
|
|
return;
|
|
if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits)
|
|
return;
|
|
|
|
va_list argptr;
|
|
char *buffer = new char[4096];
|
|
va_start(argptr, message);
|
|
vsnprintf(buffer, 4096, message, argptr);
|
|
va_end(argptr);
|
|
|
|
size_t len = strlen(buffer);
|
|
|
|
//client dosent like our packet all the time unless
|
|
//we make it really big, then it seems to not care that
|
|
//our header is malformed.
|
|
//len = 4096 - sizeof(SpecialMesg_Struct);
|
|
|
|
uint32 len_packet = sizeof(SpecialMesg_Struct)+len;
|
|
EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet);
|
|
SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer;
|
|
sm->header[0] = 0x00; // Header used for #emote style messages..
|
|
sm->header[1] = 0x00; // Play around with these to see other types
|
|
sm->header[2] = 0x00;
|
|
sm->msg_type = type;
|
|
memcpy(sm->message, buffer, len+1);
|
|
|
|
FastQueuePacket(&app);
|
|
|
|
safe_delete_array(buffer);
|
|
}
|
|
|
|
void Client::QuestJournalledMessage(const char *npcname, const char* message) {
|
|
|
|
// npcnames longer than 60 characters crash the client when they log back in
|
|
const int MaxNPCNameLength = 60;
|
|
// I assume there is an upper safe limit on the message length. Don't know what it is, but 4000 doesn't crash
|
|
// the client.
|
|
const int MaxMessageLength = 4000;
|
|
|
|
char OutNPCName[MaxNPCNameLength+1];
|
|
char OutMessage[MaxMessageLength+1];
|
|
|
|
// Apparently Visual C++ snprintf is not C99 compliant and doesn't put the null terminator
|
|
// in if the formatted string >= the maximum length, so we put it in.
|
|
//
|
|
snprintf(OutNPCName, MaxNPCNameLength, "%s", npcname); OutNPCName[MaxNPCNameLength]='\0';
|
|
snprintf(OutMessage, MaxMessageLength, "%s", message); OutMessage[MaxMessageLength]='\0';
|
|
|
|
uint32 len_packet = sizeof(SpecialMesg_Struct) + strlen(OutNPCName) + strlen(OutMessage);
|
|
EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet);
|
|
SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer;
|
|
|
|
sm->header[0] = 0;
|
|
sm->header[1] = 2;
|
|
sm->header[2] = 0;
|
|
sm->msg_type = 0x0a;
|
|
sm->target_spawn_id = GetID();
|
|
|
|
char *dest = &sm->sayer[0];
|
|
|
|
memcpy(dest, OutNPCName, strlen(OutNPCName) + 1);
|
|
|
|
dest = dest + strlen(OutNPCName) + 13;
|
|
|
|
memcpy(dest, OutMessage, strlen(OutMessage) + 1);
|
|
|
|
QueuePacket(app);
|
|
|
|
safe_delete(app);
|
|
}
|
|
|
|
void Client::SetMaxHP() {
|
|
if(dead)
|
|
return;
|
|
SetHP(CalcMaxHP());
|
|
SendHPUpdate();
|
|
Save();
|
|
}
|
|
|
|
bool Client::UpdateLDoNPoints(int32 points, uint32 theme)
|
|
{
|
|
|
|
/* make sure total stays in sync with individual buckets
|
|
m_pp.ldon_points_available = m_pp.ldon_points_guk
|
|
+m_pp.ldon_points_mir
|
|
+m_pp.ldon_points_mmc
|
|
+m_pp.ldon_points_ruj
|
|
+m_pp.ldon_points_tak; */
|
|
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_available < (0-points))
|
|
return false;
|
|
}
|
|
switch(theme)
|
|
{
|
|
// handle generic points (theme=0)
|
|
case 0:
|
|
{ // no theme, so distribute evenly across all
|
|
int splitpts=points/5;
|
|
int gukpts=splitpts+(points%5);
|
|
int mirpts=splitpts;
|
|
int mmcpts=splitpts;
|
|
int rujpts=splitpts;
|
|
int takpts=splitpts;
|
|
|
|
splitpts=0;
|
|
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_available < (0-points))
|
|
{
|
|
return false;
|
|
}
|
|
if(m_pp.ldon_points_guk < (0-gukpts))
|
|
{
|
|
mirpts+=gukpts+m_pp.ldon_points_guk;
|
|
gukpts=0-m_pp.ldon_points_guk;
|
|
}
|
|
if(m_pp.ldon_points_mir < (0-mirpts))
|
|
{
|
|
mmcpts+=mirpts+m_pp.ldon_points_mir;
|
|
mirpts=0-m_pp.ldon_points_mir;
|
|
}
|
|
if(m_pp.ldon_points_mmc < (0-mmcpts))
|
|
{
|
|
rujpts+=mmcpts+m_pp.ldon_points_mmc;
|
|
mmcpts=0-m_pp.ldon_points_mmc;
|
|
}
|
|
if(m_pp.ldon_points_ruj < (0-rujpts))
|
|
{
|
|
takpts+=rujpts+m_pp.ldon_points_ruj;
|
|
rujpts=0-m_pp.ldon_points_ruj;
|
|
}
|
|
if(m_pp.ldon_points_tak < (0-takpts))
|
|
{
|
|
splitpts=takpts+m_pp.ldon_points_tak;
|
|
takpts=0-m_pp.ldon_points_tak;
|
|
}
|
|
}
|
|
m_pp.ldon_points_guk += gukpts;
|
|
m_pp.ldon_points_mir+=mirpts;
|
|
m_pp.ldon_points_mmc += mmcpts;
|
|
m_pp.ldon_points_ruj += rujpts;
|
|
m_pp.ldon_points_tak += takpts;
|
|
points-=splitpts;
|
|
// if anything left, recursively loop thru again
|
|
if (splitpts !=0)
|
|
UpdateLDoNPoints(splitpts,0);
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_guk < (0-points))
|
|
return false;
|
|
}
|
|
m_pp.ldon_points_guk += points;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_mir < (0-points))
|
|
return false;
|
|
}
|
|
m_pp.ldon_points_mir += points;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_mmc < (0-points))
|
|
return false;
|
|
}
|
|
m_pp.ldon_points_mmc += points;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_ruj < (0-points))
|
|
return false;
|
|
}
|
|
m_pp.ldon_points_ruj += points;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
if(points < 0)
|
|
{
|
|
if(m_pp.ldon_points_tak < (0-points))
|
|
return false;
|
|
}
|
|
m_pp.ldon_points_tak += points;
|
|
break;
|
|
}
|
|
}
|
|
m_pp.ldon_points_available += points;
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventurePointsUpdate, sizeof(AdventurePoints_Update_Struct));
|
|
AdventurePoints_Update_Struct* apus = (AdventurePoints_Update_Struct*)outapp->pBuffer;
|
|
apus->ldon_available_points = m_pp.ldon_points_available;
|
|
apus->ldon_guk_points = m_pp.ldon_points_guk;
|
|
apus->ldon_mirugal_points = m_pp.ldon_points_mir;
|
|
apus->ldon_mistmoore_points = m_pp.ldon_points_mmc;
|
|
apus->ldon_rujarkian_points = m_pp.ldon_points_ruj;
|
|
apus->ldon_takish_points = m_pp.ldon_points_tak;
|
|
outapp->priority = 6;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
return true;
|
|
|
|
return(false);
|
|
}
|
|
|
|
void Client::SetSkill(SkillType skillid, uint16 value) {
|
|
if (skillid > HIGHEST_SKILL)
|
|
return;
|
|
m_pp.skills[skillid] = value; // We need to be able to #setskill 254 and 255 to reset skills
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct));
|
|
SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer;
|
|
skill->skillId=skillid;
|
|
skill->value=value;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::IncreaseLanguageSkill(int skill_id, int value) {
|
|
|
|
if (skill_id >= MAX_PP_LANGUAGE)
|
|
return; //Invalid lang id
|
|
|
|
m_pp.languages[skill_id] += value;
|
|
|
|
if (m_pp.languages[skill_id] > 100) //Lang skill above max
|
|
m_pp.languages[skill_id] = 100;
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct));
|
|
SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer;
|
|
skill->skillId = 100 + skill_id;
|
|
skill->value = m_pp.languages[skill_id];
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify client
|
|
}
|
|
|
|
void Client::AddSkill(SkillType skillid, uint16 value) {
|
|
if (skillid > HIGHEST_SKILL)
|
|
return;
|
|
value = GetRawSkill(skillid) + value;
|
|
uint16 max = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid));
|
|
if (value > max)
|
|
value = max;
|
|
SetSkill(skillid, value);
|
|
}
|
|
|
|
void Client::SendSound(){//Makes a sound.
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, 68);
|
|
unsigned char x[68];
|
|
memset(x, 0, 68);
|
|
x[0]=0x22;
|
|
memset(&x[4],0x8002,sizeof(uint16));
|
|
memset(&x[8],0x8624,sizeof(uint16));
|
|
memset(&x[12],0x4A01,sizeof(uint16));
|
|
x[16]=0x05;
|
|
x[28]=0x00;//change this value to give gold to the client
|
|
memset(&x[40],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[44],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[48],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[52],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[56],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[60],0xFFFFFFFF,sizeof(uint32));
|
|
memset(&x[64],0xffffffff,sizeof(uint32));
|
|
memcpy(outapp->pBuffer,x,outapp->size);
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
}
|
|
void Client::UpdateWho(uint8 remove) {
|
|
if (account_id == 0)
|
|
return;
|
|
if (!worldserver.Connected())
|
|
return;
|
|
ServerPacket* pack = new ServerPacket(ServerOP_ClientList, sizeof(ServerClientList_Struct));
|
|
ServerClientList_Struct* scl = (ServerClientList_Struct*) pack->pBuffer;
|
|
scl->remove = remove;
|
|
scl->wid = this->GetWID();
|
|
scl->IP = this->GetIP();
|
|
scl->charid = this->CharacterID();
|
|
strcpy(scl->name, this->GetName());
|
|
|
|
scl->gm = GetGM();
|
|
scl->Admin = this->Admin();
|
|
scl->AccountID = this->AccountID();
|
|
strcpy(scl->AccountName, this->AccountName());
|
|
scl->LSAccountID = this->LSAccountID();
|
|
strn0cpy(scl->lskey, lskey, sizeof(scl->lskey));
|
|
scl->zone = zone->GetZoneID();
|
|
scl->instance_id = zone->GetInstanceID();
|
|
scl->race = this->GetRace();
|
|
scl->class_ = GetClass();
|
|
scl->level = GetLevel();
|
|
if (m_pp.anon == 0)
|
|
scl->anon = 0;
|
|
else if (m_pp.anon == 1)
|
|
scl->anon = 1;
|
|
else if (m_pp.anon >= 2)
|
|
scl->anon = 2;
|
|
|
|
scl->ClientVersion = GetClientVersion();
|
|
scl->tellsoff = tellsoff;
|
|
scl->guild_id = guild_id;
|
|
scl->LFG = LFG;
|
|
if(LFG) {
|
|
scl->LFGFromLevel = LFGFromLevel;
|
|
scl->LFGToLevel = LFGToLevel;
|
|
scl->LFGMatchFilter = LFGMatchFilter;
|
|
memcpy(scl->LFGComments, LFGComments, sizeof(scl->LFGComments));
|
|
}
|
|
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
|
|
void Client::WhoAll(Who_All_Struct* whom) {
|
|
|
|
if (!worldserver.Connected())
|
|
Message(0, "Error: World server disconnected");
|
|
else {
|
|
ServerPacket* pack = new ServerPacket(ServerOP_Who, sizeof(ServerWhoAll_Struct));
|
|
ServerWhoAll_Struct* whoall = (ServerWhoAll_Struct*) pack->pBuffer;
|
|
whoall->admin = this->Admin();
|
|
whoall->fromid=this->GetID();
|
|
strcpy(whoall->from, this->GetName());
|
|
strn0cpy(whoall->whom, whom->whom, 64);
|
|
whoall->lvllow = whom->lvllow;
|
|
whoall->lvlhigh = whom->lvlhigh;
|
|
whoall->gmlookup = whom->gmlookup;
|
|
whoall->wclass = whom->wclass;
|
|
whoall->wrace = whom->wrace;
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
}
|
|
|
|
void Client::FriendsWho(char *FriendsString) {
|
|
|
|
if (!worldserver.Connected())
|
|
Message(0, "Error: World server disconnected");
|
|
else {
|
|
ServerPacket* pack = new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString));
|
|
ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer;
|
|
FriendsWho->FromID = this->GetID();
|
|
strcpy(FriendsWho->FromName, GetName());
|
|
strcpy(FriendsWho->FriendsString, FriendsString);
|
|
worldserver.SendPacket(pack);
|
|
safe_delete(pack);
|
|
}
|
|
}
|
|
|
|
|
|
void Client::UpdateAdmin(bool iFromDB) {
|
|
int16 tmp = admin;
|
|
if (iFromDB)
|
|
admin = database.CheckStatus(account_id);
|
|
if (tmp == admin && iFromDB)
|
|
return;
|
|
|
|
if(m_pp.gm)
|
|
{
|
|
#if EQDEBUG >= 5
|
|
printf("%s is a GM\n", GetName());
|
|
#endif
|
|
// no need for this, having it set in pp you already start as gm
|
|
// and it's also set in your spawn packet so other people see it too
|
|
// SendAppearancePacket(AT_GM, 1, false);
|
|
petition_list.UpdateGMQueue();
|
|
}
|
|
|
|
UpdateWho();
|
|
}
|
|
|
|
void Client::SetStats(uint8 type,int16 set_val){
|
|
if(type>STAT_DISEASE){
|
|
printf("Error in Client::IncStats, received invalid type of: %i\n",type);
|
|
return;
|
|
}
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct));
|
|
IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer;
|
|
switch(type){
|
|
case STAT_STR:
|
|
if(set_val>0)
|
|
iss->str=set_val;
|
|
if(set_val<0)
|
|
m_pp.STR=0;
|
|
else if(set_val>255)
|
|
m_pp.STR=255;
|
|
else
|
|
m_pp.STR=set_val;
|
|
break;
|
|
case STAT_STA:
|
|
if(set_val>0)
|
|
iss->sta=set_val;
|
|
if(set_val<0)
|
|
m_pp.STA=0;
|
|
else if(set_val>255)
|
|
m_pp.STA=255;
|
|
else
|
|
m_pp.STA=set_val;
|
|
break;
|
|
case STAT_AGI:
|
|
if(set_val>0)
|
|
iss->agi=set_val;
|
|
if(set_val<0)
|
|
m_pp.AGI=0;
|
|
else if(set_val>255)
|
|
m_pp.AGI=255;
|
|
else
|
|
m_pp.AGI=set_val;
|
|
break;
|
|
case STAT_DEX:
|
|
if(set_val>0)
|
|
iss->dex=set_val;
|
|
if(set_val<0)
|
|
m_pp.DEX=0;
|
|
else if(set_val>255)
|
|
m_pp.DEX=255;
|
|
else
|
|
m_pp.DEX=set_val;
|
|
break;
|
|
case STAT_INT:
|
|
if(set_val>0)
|
|
iss->int_=set_val;
|
|
if(set_val<0)
|
|
m_pp.INT=0;
|
|
else if(set_val>255)
|
|
m_pp.INT=255;
|
|
else
|
|
m_pp.INT=set_val;
|
|
break;
|
|
case STAT_WIS:
|
|
if(set_val>0)
|
|
iss->wis=set_val;
|
|
if(set_val<0)
|
|
m_pp.WIS=0;
|
|
else if(set_val>255)
|
|
m_pp.WIS=255;
|
|
else
|
|
m_pp.WIS=set_val;
|
|
break;
|
|
case STAT_CHA:
|
|
if(set_val>0)
|
|
iss->cha=set_val;
|
|
if(set_val<0)
|
|
m_pp.CHA=0;
|
|
else if(set_val>255)
|
|
m_pp.CHA=255;
|
|
else
|
|
m_pp.CHA=set_val;
|
|
break;
|
|
}
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::IncStats(uint8 type,int16 increase_val){
|
|
if(type>STAT_DISEASE){
|
|
printf("Error in Client::IncStats, received invalid type of: %i\n",type);
|
|
return;
|
|
}
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct));
|
|
IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer;
|
|
switch(type){
|
|
case STAT_STR:
|
|
if(increase_val>0)
|
|
iss->str=increase_val;
|
|
if((m_pp.STR+increase_val*2)<0)
|
|
m_pp.STR=0;
|
|
else if((m_pp.STR+increase_val*2)>255)
|
|
m_pp.STR=255;
|
|
else
|
|
m_pp.STR+=increase_val*2;
|
|
break;
|
|
case STAT_STA:
|
|
if(increase_val>0)
|
|
iss->sta=increase_val;
|
|
if((m_pp.STA+increase_val*2)<0)
|
|
m_pp.STA=0;
|
|
else if((m_pp.STA+increase_val*2)>255)
|
|
m_pp.STA=255;
|
|
else
|
|
m_pp.STA+=increase_val*2;
|
|
break;
|
|
case STAT_AGI:
|
|
if(increase_val>0)
|
|
iss->agi=increase_val;
|
|
if((m_pp.AGI+increase_val*2)<0)
|
|
m_pp.AGI=0;
|
|
else if((m_pp.AGI+increase_val*2)>255)
|
|
m_pp.AGI=255;
|
|
else
|
|
m_pp.AGI+=increase_val*2;
|
|
break;
|
|
case STAT_DEX:
|
|
if(increase_val>0)
|
|
iss->dex=increase_val;
|
|
if((m_pp.DEX+increase_val*2)<0)
|
|
m_pp.DEX=0;
|
|
else if((m_pp.DEX+increase_val*2)>255)
|
|
m_pp.DEX=255;
|
|
else
|
|
m_pp.DEX+=increase_val*2;
|
|
break;
|
|
case STAT_INT:
|
|
if(increase_val>0)
|
|
iss->int_=increase_val;
|
|
if((m_pp.INT+increase_val*2)<0)
|
|
m_pp.INT=0;
|
|
else if((m_pp.INT+increase_val*2)>255)
|
|
m_pp.INT=255;
|
|
else
|
|
m_pp.INT+=increase_val*2;
|
|
break;
|
|
case STAT_WIS:
|
|
if(increase_val>0)
|
|
iss->wis=increase_val;
|
|
if((m_pp.WIS+increase_val*2)<0)
|
|
m_pp.WIS=0;
|
|
else if((m_pp.WIS+increase_val*2)>255)
|
|
m_pp.WIS=255;
|
|
else
|
|
m_pp.WIS+=increase_val*2;
|
|
break;
|
|
case STAT_CHA:
|
|
if(increase_val>0)
|
|
iss->cha=increase_val;
|
|
if((m_pp.CHA+increase_val*2)<0)
|
|
m_pp.CHA=0;
|
|
else if((m_pp.CHA+increase_val*2)>255)
|
|
m_pp.CHA=255;
|
|
else
|
|
m_pp.CHA+=increase_val*2;
|
|
break;
|
|
}
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
const int32& Client::SetMana(int32 amount) {
|
|
bool update = false;
|
|
if (amount < 0)
|
|
amount = 0;
|
|
if (amount > GetMaxMana())
|
|
amount = GetMaxMana();
|
|
if (amount != cur_mana)
|
|
update = true;
|
|
cur_mana = amount;
|
|
if (update)
|
|
Mob::SetMana(amount);
|
|
SendManaUpdatePacket();
|
|
return cur_mana;
|
|
}
|
|
|
|
void Client::SendManaUpdatePacket() {
|
|
if (!Connected() || IsCasting())
|
|
return;
|
|
|
|
if (GetClientVersion() >= EQClientSoD) {
|
|
SendManaUpdate();
|
|
SendEnduranceUpdate();
|
|
}
|
|
|
|
//cout << "Sending mana update: " << (cur_mana - last_reported_mana) << endl;
|
|
if (last_reported_mana != cur_mana || last_reported_endur != cur_end) {
|
|
|
|
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ManaChange, sizeof(ManaChange_Struct));
|
|
ManaChange_Struct* manachange = (ManaChange_Struct*)outapp->pBuffer;
|
|
manachange->new_mana = cur_mana;
|
|
manachange->stamina = cur_end;
|
|
manachange->spell_id = casting_spell_id; //always going to be 0... since we check IsCasting()
|
|
outapp->priority = 6;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
Group *g = GetGroup();
|
|
|
|
if(g)
|
|
{
|
|
outapp = new EQApplicationPacket(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct));
|
|
EQApplicationPacket *outapp2 = new EQApplicationPacket(OP_MobEnduranceUpdate, sizeof(MobEnduranceUpdate_Struct));
|
|
|
|
MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp->pBuffer;
|
|
MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp2->pBuffer;
|
|
|
|
mmus->spawn_id = meus->spawn_id = GetID();
|
|
|
|
mmus->mana = GetManaPercent();
|
|
meus->endurance = GetEndurancePercent();
|
|
|
|
|
|
for(int i = 0; i < MAX_GROUP_MEMBERS; ++i)
|
|
if(g->members[i] && g->members[i]->IsClient() && (g->members[i] != this) && (g->members[i]->CastToClient()->GetClientVersion() >= EQClientSoD))
|
|
{
|
|
g->members[i]->CastToClient()->QueuePacket(outapp);
|
|
g->members[i]->CastToClient()->QueuePacket(outapp2);
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
safe_delete(outapp2);
|
|
}
|
|
|
|
|
|
last_reported_mana = cur_mana;
|
|
last_reported_endur = cur_end;
|
|
}
|
|
}
|
|
|
|
// sends mana update to self
|
|
void Client::SendManaUpdate()
|
|
{
|
|
EQApplicationPacket* mana_app = new EQApplicationPacket(OP_ManaUpdate,sizeof(ManaUpdate_Struct));
|
|
ManaUpdate_Struct* mus = (ManaUpdate_Struct*)mana_app->pBuffer;
|
|
mus->cur_mana = GetMana();
|
|
mus->max_mana = GetMaxMana();
|
|
mus->spawn_id = GetID();
|
|
QueuePacket(mana_app);
|
|
entity_list.QueueClientsByXTarget(this, mana_app, false);
|
|
safe_delete(mana_app);
|
|
}
|
|
|
|
// sends endurance update to self
|
|
void Client::SendEnduranceUpdate()
|
|
{
|
|
EQApplicationPacket* end_app = new EQApplicationPacket(OP_EnduranceUpdate,sizeof(EnduranceUpdate_Struct));
|
|
EnduranceUpdate_Struct* eus = (EnduranceUpdate_Struct*)end_app->pBuffer;
|
|
eus->cur_end = GetEndurance();
|
|
eus->max_end = GetMaxEndurance();
|
|
eus->spawn_id = GetID();
|
|
QueuePacket(end_app);
|
|
entity_list.QueueClientsByXTarget(this, end_app, false);
|
|
safe_delete(end_app);
|
|
}
|
|
|
|
void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
|
{
|
|
Mob::FillSpawnStruct(ns, ForWho);
|
|
|
|
// Populate client-specific spawn information
|
|
ns->spawn.afk = AFK;
|
|
ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live
|
|
ns->spawn.anon = m_pp.anon;
|
|
ns->spawn.gm = GetGM() ? 1 : 0;
|
|
ns->spawn.guildID = GuildID();
|
|
// ns->spawn.linkdead = IsLD() ? 1 : 0;
|
|
// ns->spawn.pvp = GetPVP() ? 1 : 0;
|
|
|
|
|
|
strcpy(ns->spawn.title, m_pp.title);
|
|
strcpy(ns->spawn.suffix, m_pp.suffix);
|
|
|
|
if (IsBecomeNPC() == true)
|
|
ns->spawn.NPC = 1;
|
|
else if (ForWho == this)
|
|
ns->spawn.NPC = 10;
|
|
else
|
|
ns->spawn.NPC = 0;
|
|
ns->spawn.is_pet = 0;
|
|
|
|
if (!IsInAGuild()) {
|
|
ns->spawn.guildrank = 0xFF;
|
|
} else {
|
|
ns->spawn.guildrank = guild_mgr.GetDisplayedRank(GuildID(), GuildRank(), AccountID());
|
|
}
|
|
ns->spawn.size = 0; // Changing size works, but then movement stops! (wth?)
|
|
ns->spawn.runspeed = (gmspeed == 0) ? runspeed : 3.125f;
|
|
if (!m_pp.showhelm) ns->spawn.showhelm = 0;
|
|
|
|
// pp also hold this info; should we pull from there or inventory?
|
|
// (update: i think pp should do it, as this holds LoY dye - plus, this is ugly code with Inventory!)
|
|
const Item_Struct* item = nullptr;
|
|
const ItemInst* inst = nullptr;
|
|
if ((inst = m_inv[SLOT_HANDS]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_HANDS] = item->Material;
|
|
ns->spawn.colors[MATERIAL_HANDS].color = GetEquipmentColor(MATERIAL_HANDS);
|
|
}
|
|
if ((inst = m_inv[SLOT_HEAD]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_HEAD] = item->Material;
|
|
ns->spawn.colors[MATERIAL_HEAD].color = GetEquipmentColor(MATERIAL_HEAD);
|
|
}
|
|
if ((inst = m_inv[SLOT_ARMS]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_ARMS] = item->Material;
|
|
ns->spawn.colors[MATERIAL_ARMS].color = GetEquipmentColor(MATERIAL_ARMS);
|
|
}
|
|
if ((inst = m_inv[SLOT_BRACER01]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_BRACER]= item->Material;
|
|
ns->spawn.colors[MATERIAL_BRACER].color = GetEquipmentColor(MATERIAL_BRACER);
|
|
}
|
|
if ((inst = m_inv[SLOT_BRACER02]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_BRACER]= item->Material;
|
|
ns->spawn.colors[MATERIAL_BRACER].color = GetEquipmentColor(MATERIAL_BRACER);
|
|
}
|
|
if ((inst = m_inv[SLOT_CHEST]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_CHEST] = item->Material;
|
|
ns->spawn.colors[MATERIAL_CHEST].color = GetEquipmentColor(MATERIAL_CHEST);
|
|
}
|
|
if ((inst = m_inv[SLOT_LEGS]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_LEGS] = item->Material;
|
|
ns->spawn.colors[MATERIAL_LEGS].color = GetEquipmentColor(MATERIAL_LEGS);
|
|
}
|
|
if ((inst = m_inv[SLOT_FEET]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
ns->spawn.equipment[MATERIAL_FEET] = item->Material;
|
|
ns->spawn.colors[MATERIAL_FEET].color = GetEquipmentColor(MATERIAL_FEET);
|
|
}
|
|
if ((inst = m_inv[SLOT_PRIMARY]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
if (strlen(item->IDFile) > 2)
|
|
ns->spawn.equipment[MATERIAL_PRIMARY] = atoi(&item->IDFile[2]);
|
|
}
|
|
if ((inst = m_inv[SLOT_SECONDARY]) && inst->IsType(ItemClassCommon)) {
|
|
item = inst->GetItem();
|
|
if (strlen(item->IDFile) > 2)
|
|
ns->spawn.equipment[MATERIAL_SECONDARY] = atoi(&item->IDFile[2]);
|
|
}
|
|
|
|
//these two may be related to ns->spawn.texture
|
|
/*
|
|
ns->spawn.npc_armor_graphic = texture;
|
|
ns->spawn.npc_helm_graphic = helmtexture;
|
|
*/
|
|
|
|
//filling in some unknowns to make the client happy
|
|
// ns->spawn.unknown0002[2] = 3;
|
|
|
|
}
|
|
|
|
bool Client::GMHideMe(Client* client) {
|
|
if (gmhideme) {
|
|
if (client == 0)
|
|
return true;
|
|
else if (admin > client->Admin())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Client::Duck() {
|
|
SetAppearance(eaCrouching, false);
|
|
}
|
|
|
|
void Client::Stand() {
|
|
SetAppearance(eaStanding, false);
|
|
}
|
|
|
|
void Client::ChangeLastName(const char* in_lastname) {
|
|
memset(m_pp.last_name, 0, sizeof(m_pp.last_name));
|
|
strn0cpy(m_pp.last_name, in_lastname, sizeof(m_pp.last_name));
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct));
|
|
GMLastName_Struct* gmn = (GMLastName_Struct*)outapp->pBuffer;
|
|
strcpy(gmn->name, name);
|
|
strcpy(gmn->gmname, name);
|
|
strcpy(gmn->lastname, in_lastname);
|
|
gmn->unknown[0]=1;
|
|
gmn->unknown[1]=1;
|
|
gmn->unknown[2]=1;
|
|
gmn->unknown[3]=1;
|
|
entity_list.QueueClients(this, outapp, false);
|
|
// Send name update packet here... once know what it is
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
|
|
{
|
|
// check duplicate name
|
|
bool usedname = database.CheckUsedName((const char*) in_firstname);
|
|
if (!usedname) {
|
|
return false;
|
|
}
|
|
|
|
// update character_
|
|
if(!database.UpdateName(GetName(), in_firstname))
|
|
return false;
|
|
|
|
// update pp
|
|
memset(m_pp.name, 0, sizeof(m_pp.name));
|
|
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
|
|
strcpy(name, m_pp.name);
|
|
Save();
|
|
|
|
// send name update packet
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
|
|
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
|
|
strn0cpy(gmn->gmname,gmname,64);
|
|
strn0cpy(gmn->oldname,GetName(),64);
|
|
strn0cpy(gmn->newname,in_firstname,64);
|
|
gmn->unknown[0] = 1;
|
|
gmn->unknown[1] = 1;
|
|
gmn->unknown[2] = 1;
|
|
entity_list.QueueClients(this, outapp, false);
|
|
safe_delete(outapp);
|
|
|
|
// finally, update the /who list
|
|
UpdateWho();
|
|
|
|
// success
|
|
return true;
|
|
}
|
|
|
|
void Client::SetGM(bool toggle) {
|
|
m_pp.gm = toggle ? 1 : 0;
|
|
Message(13, "You are %s a GM.", m_pp.gm ? "now" : "no longer");
|
|
SendAppearancePacket(AT_GM, m_pp.gm);
|
|
Save();
|
|
UpdateWho();
|
|
}
|
|
|
|
void Client::ReadBook(BookRequest_Struct *book) {
|
|
char *txtfile = book->txtfile;
|
|
|
|
if(txtfile[0] == '0' && txtfile[1] == '\0') {
|
|
//invalid book... coming up on non-book items.
|
|
return;
|
|
}
|
|
|
|
string booktxt2 = database.GetBook(txtfile);
|
|
int length = booktxt2.length();
|
|
|
|
if (booktxt2[0] != '\0') {
|
|
#if EQDEBUG >= 6
|
|
LogFile->write(EQEMuLog::Normal,"Client::ReadBook() textfile:%s Text:%s", txtfile, booktxt2.c_str());
|
|
#endif
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct));
|
|
|
|
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer;
|
|
out->window = book->window;
|
|
if(GetClientVersion() >= EQClientSoF)
|
|
{
|
|
const ItemInst *inst = m_inv[book->invslot];
|
|
if(inst)
|
|
out->type = inst->GetItem()->Book;
|
|
else
|
|
out->type = book->type;
|
|
}
|
|
else
|
|
{
|
|
out->type = book->type;
|
|
}
|
|
out->invslot = book->invslot;
|
|
memcpy(out->booktext, booktxt2.c_str(), length);
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
}
|
|
|
|
void Client::QuestReadBook(const char* text, uint8 type) {
|
|
string booktxt2 = text;
|
|
int length = booktxt2.length();
|
|
if (booktxt2[0] != '\0') {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct));
|
|
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer;
|
|
out->window = 0xFF;
|
|
out->type = type;
|
|
out->invslot = 0;
|
|
memcpy(out->booktext, booktxt2.c_str(), length);
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
}
|
|
|
|
void Client::SendClientMoneyUpdate(uint8 type,uint32 amount){
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeMoneyUpdate,sizeof(TradeMoneyUpdate_Struct));
|
|
TradeMoneyUpdate_Struct* mus= (TradeMoneyUpdate_Struct*)outapp->pBuffer;
|
|
mus->amount=amount;
|
|
mus->trader=0;
|
|
mus->type=type;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
bool Client::TakeMoneyFromPP(uint64 copper, bool updateclient) {
|
|
int64 copperpp,silver,gold,platinum;
|
|
copperpp = m_pp.copper;
|
|
silver = static_cast<int64>(m_pp.silver) * 10;
|
|
gold = static_cast<int64>(m_pp.gold) * 100;
|
|
platinum = static_cast<int64>(m_pp.platinum) * 1000;
|
|
|
|
int64 clienttotal = copperpp + silver + gold + platinum;
|
|
|
|
clienttotal -= copper;
|
|
if(clienttotal < 0)
|
|
{
|
|
return false; // Not enough money!
|
|
}
|
|
else
|
|
{
|
|
copperpp -= copper;
|
|
if(copperpp <= 0)
|
|
{
|
|
copper = abs64(copperpp);
|
|
m_pp.copper = 0;
|
|
}
|
|
else
|
|
{
|
|
m_pp.copper = copperpp;
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
Save();
|
|
return true;
|
|
}
|
|
silver -= copper;
|
|
if(silver <= 0)
|
|
{
|
|
copper = abs64(silver);
|
|
m_pp.silver = 0;
|
|
}
|
|
else
|
|
{
|
|
m_pp.silver = silver/10;
|
|
m_pp.copper += (silver-(m_pp.silver*10));
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
Save();
|
|
return true;
|
|
}
|
|
|
|
gold -=copper;
|
|
|
|
if(gold <= 0)
|
|
{
|
|
copper = abs64(gold);
|
|
m_pp.gold = 0;
|
|
}
|
|
else
|
|
{
|
|
m_pp.gold = gold/100;
|
|
uint64 silvertest = (gold-(static_cast<uint64>(m_pp.gold)*100))/10;
|
|
m_pp.silver += silvertest;
|
|
uint64 coppertest = (gold-(static_cast<uint64>(m_pp.gold)*100+silvertest*10));
|
|
m_pp.copper += coppertest;
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
Save();
|
|
return true;
|
|
}
|
|
|
|
platinum -= copper;
|
|
|
|
//Impossible for plat to be negative, already checked above
|
|
|
|
m_pp.platinum = platinum/1000;
|
|
uint64 goldtest = (platinum-(static_cast<uint64>(m_pp.platinum)*1000))/100;
|
|
m_pp.gold += goldtest;
|
|
uint64 silvertest = (platinum-(static_cast<uint64>(m_pp.platinum)*1000+goldtest*100))/10;
|
|
m_pp.silver += silvertest;
|
|
uint64 coppertest = (platinum-(static_cast<uint64>(m_pp.platinum)*1000+goldtest*100+silvertest*10));
|
|
m_pp.copper = coppertest;
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
RecalcWeight();
|
|
Save();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void Client::AddMoneyToPP(uint64 copper, bool updateclient){
|
|
uint64 tmp;
|
|
uint64 tmp2;
|
|
tmp = copper;
|
|
|
|
// Add Amount of Platinum
|
|
tmp2 = tmp/1000;
|
|
int32 new_val = m_pp.platinum + tmp2;
|
|
if(new_val < 0) {
|
|
m_pp.platinum = 0;
|
|
} else {
|
|
m_pp.platinum = m_pp.platinum + tmp2;
|
|
}
|
|
tmp-=tmp2*1000;
|
|
|
|
//if (updateclient)
|
|
// SendClientMoneyUpdate(3,tmp2);
|
|
|
|
// Add Amount of Gold
|
|
tmp2 = tmp/100;
|
|
new_val = m_pp.gold + tmp2;
|
|
if(new_val < 0) {
|
|
m_pp.gold = 0;
|
|
} else {
|
|
m_pp.gold = m_pp.gold + tmp2;
|
|
}
|
|
tmp-=tmp2*100;
|
|
//if (updateclient)
|
|
// SendClientMoneyUpdate(2,tmp2);
|
|
|
|
// Add Amount of Silver
|
|
tmp2 = tmp/10;
|
|
new_val = m_pp.silver + tmp2;
|
|
if(new_val < 0) {
|
|
m_pp.silver = 0;
|
|
} else {
|
|
m_pp.silver = m_pp.silver + tmp2;
|
|
}
|
|
tmp-=tmp2*10;
|
|
//if (updateclient)
|
|
// SendClientMoneyUpdate(1,tmp2);
|
|
|
|
// Add Copper
|
|
//tmp = tmp - (tmp2* 10);
|
|
//if (updateclient)
|
|
// SendClientMoneyUpdate(0,tmp);
|
|
tmp2 = tmp;
|
|
new_val = m_pp.copper + tmp2;
|
|
if(new_val < 0) {
|
|
m_pp.copper = 0;
|
|
} else {
|
|
m_pp.copper = m_pp.copper + tmp2;
|
|
}
|
|
|
|
|
|
//send them all at once, since the above code stopped working.
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
|
|
RecalcWeight();
|
|
|
|
Save();
|
|
|
|
LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
|
}
|
|
|
|
void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool updateclient){
|
|
|
|
int32 new_value = m_pp.platinum + platinum;
|
|
if(new_value >= 0 && new_value > m_pp.platinum)
|
|
m_pp.platinum += platinum;
|
|
|
|
new_value = m_pp.gold + gold;
|
|
if(new_value >= 0 && new_value > m_pp.gold)
|
|
m_pp.gold += gold;
|
|
|
|
new_value = m_pp.silver + silver;
|
|
if(new_value >= 0 && new_value > m_pp.silver)
|
|
m_pp.silver += silver;
|
|
|
|
new_value = m_pp.copper + copper;
|
|
if(new_value >= 0 && new_value > m_pp.copper)
|
|
m_pp.copper += copper;
|
|
|
|
if(updateclient)
|
|
SendMoneyUpdate();
|
|
|
|
RecalcWeight();
|
|
Save();
|
|
|
|
#if (EQDEBUG>=5)
|
|
LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i",
|
|
GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
|
#endif
|
|
}
|
|
|
|
void Client::SendMoneyUpdate() {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyUpdate,sizeof(MoneyUpdate_Struct));
|
|
MoneyUpdate_Struct* mus= (MoneyUpdate_Struct*)outapp->pBuffer;
|
|
|
|
mus->platinum = m_pp.platinum;
|
|
mus->gold = m_pp.gold;
|
|
mus->silver = m_pp.silver;
|
|
mus->copper = m_pp.copper;
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
bool Client::HasMoney(uint64 Copper) {
|
|
|
|
if((static_cast<uint64>(m_pp.copper) +
|
|
(static_cast<uint64>(m_pp.silver) * 10) +
|
|
(static_cast<uint64>(m_pp.gold) * 100) +
|
|
(static_cast<uint64>(m_pp.platinum) * 1000)) >= Copper)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
uint64 Client::GetCarriedMoney() {
|
|
|
|
return ((static_cast<uint64>(m_pp.copper) +
|
|
(static_cast<uint64>(m_pp.silver) * 10) +
|
|
(static_cast<uint64>(m_pp.gold) * 100) +
|
|
(static_cast<uint64>(m_pp.platinum) * 1000)));
|
|
}
|
|
|
|
uint64 Client::GetAllMoney() {
|
|
|
|
return (
|
|
(static_cast<uint64>(m_pp.copper) +
|
|
(static_cast<uint64>(m_pp.silver) * 10) +
|
|
(static_cast<uint64>(m_pp.gold) * 100) +
|
|
(static_cast<uint64>(m_pp.platinum) * 1000) +
|
|
(static_cast<uint64>(m_pp.copper_bank) +
|
|
(static_cast<uint64>(m_pp.silver_bank) * 10) +
|
|
(static_cast<uint64>(m_pp.gold_bank) * 100) +
|
|
(static_cast<uint64>(m_pp.platinum_bank) * 1000) +
|
|
(static_cast<uint64>(m_pp.copper_cursor) +
|
|
(static_cast<uint64>(m_pp.silver_cursor) * 10) +
|
|
(static_cast<uint64>(m_pp.gold_cursor) * 100) +
|
|
(static_cast<uint64>(m_pp.platinum_cursor) * 1000) +
|
|
(static_cast<uint64>(m_pp.platinum_shared) * 1000)))));
|
|
}
|
|
|
|
bool Client::CheckIncreaseSkill(SkillType skillid, Mob *against_who, int chancemodi) {
|
|
if (IsAIControlled()) // no skillups while chamred =p
|
|
return false;
|
|
if (skillid > HIGHEST_SKILL)
|
|
return false;
|
|
int skillval = GetRawSkill(skillid);
|
|
int maxskill = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid));
|
|
|
|
if(against_who)
|
|
{
|
|
if(against_who->SpecAttacks[IMMUNE_AGGRO] || against_who->IsClient() ||
|
|
GetLevelCon(against_who->GetLevel()) == CON_GREEN)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Make sure we're not already at skill cap
|
|
if (skillval < maxskill)
|
|
{
|
|
// the higher your current skill level, the harder it is
|
|
int16 Chance = 10 + chancemodi + ((252 - skillval) / 20);
|
|
if (Chance < 1)
|
|
Chance = 1; // Make it always possible
|
|
Chance = (Chance * RuleI(Character, SkillUpModifier) / 100);
|
|
if(MakeRandomFloat(0, 99) < Chance)
|
|
{
|
|
SetSkill(skillid, GetRawSkill(skillid) + 1);
|
|
_log(SKILLS__GAIN, "Skill %d at value %d successfully gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi);
|
|
return true;
|
|
} else {
|
|
_log(SKILLS__GAIN, "Skill %d at value %d failed to gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi);
|
|
}
|
|
} else {
|
|
_log(SKILLS__GAIN, "Skill %d at value %d cannot increase due to maxmum %d", skillid, skillval, maxskill);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::CheckLanguageSkillIncrease(uint8 langid, uint8 TeacherSkill) {
|
|
if (langid >= MAX_PP_LANGUAGE)
|
|
return; // do nothing if langid is an invalid language
|
|
|
|
int LangSkill = m_pp.languages[langid]; // get current language skill
|
|
|
|
if (LangSkill < 100) { // if the language isn't already maxed
|
|
int16 Chance = 5 + ((TeacherSkill - LangSkill)/10); // greater chance to learn if teacher's skill is much higher than yours
|
|
Chance = (Chance * RuleI(Character, SkillUpModifier)/100);
|
|
|
|
if(MakeRandomFloat(0,100) < Chance) { // if they make the roll
|
|
IncreaseLanguageSkill(langid); // increase the language skill by 1
|
|
_log(SKILLS__GAIN, "Language %d at value %d successfully gain with %.4f%%chance", langid, LangSkill, Chance);
|
|
}
|
|
else
|
|
_log(SKILLS__GAIN, "Language %d at value %d failed to gain with %.4f%%chance", langid, LangSkill, Chance);
|
|
}
|
|
}
|
|
|
|
bool Client::HasSkill(SkillType skill_id) const {
|
|
return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id));
|
|
}
|
|
|
|
bool Client::CanHaveSkill(SkillType skill_id) const {
|
|
return(database.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0);
|
|
//if you don't have it by max level, then odds are you never will?
|
|
}
|
|
|
|
uint16 Client::MaxSkill(SkillType skillid, uint16 class_, uint16 level) const {
|
|
return(database.GetSkillCap(class_, skillid, level));
|
|
}
|
|
|
|
uint8 Client::SkillTrainLevel(SkillType skillid, uint16 class_){
|
|
return(database.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel)));
|
|
}
|
|
|
|
uint16 Client::GetMaxSkillAfterSpecializationRules(SkillType skillid, uint16 maxSkill)
|
|
{
|
|
uint16 Result = maxSkill;
|
|
|
|
uint16 PrimarySpecialization = 0, SecondaryForte = 0;
|
|
|
|
uint16 PrimarySkillValue = 0, SecondarySkillValue = 0;
|
|
|
|
uint16 MaxSpecializations = GetAA(aaSecondaryForte) ? 2 : 1;
|
|
|
|
if(skillid >= SPECIALIZE_ABJURE && skillid <= SPECIALIZE_EVOCATION)
|
|
{
|
|
bool HasPrimarySpecSkill = false;
|
|
|
|
int NumberOfPrimarySpecSkills = 0;
|
|
|
|
for(int i = SPECIALIZE_ABJURE; i <= SPECIALIZE_EVOCATION; ++i)
|
|
{
|
|
if(m_pp.skills[i] > 50)
|
|
{
|
|
HasPrimarySpecSkill = true;
|
|
NumberOfPrimarySpecSkills++;
|
|
}
|
|
if(m_pp.skills[i] > PrimarySkillValue)
|
|
{
|
|
if(PrimarySkillValue > SecondarySkillValue)
|
|
{
|
|
SecondarySkillValue = PrimarySkillValue;
|
|
SecondaryForte = PrimarySpecialization;
|
|
}
|
|
|
|
PrimarySpecialization = i;
|
|
PrimarySkillValue = m_pp.skills[i];
|
|
}
|
|
else if(m_pp.skills[i] > SecondarySkillValue)
|
|
{
|
|
SecondaryForte = i;
|
|
SecondarySkillValue = m_pp.skills[i];
|
|
}
|
|
}
|
|
|
|
if(SecondarySkillValue <=50)
|
|
SecondaryForte = 0;
|
|
|
|
if(HasPrimarySpecSkill)
|
|
{
|
|
if(NumberOfPrimarySpecSkills <= MaxSpecializations)
|
|
{
|
|
if(MaxSpecializations == 1)
|
|
{
|
|
if(skillid != PrimarySpecialization)
|
|
{
|
|
Result = 50;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((skillid != PrimarySpecialization) && ((skillid == SecondaryForte) || (SecondaryForte == 0)))
|
|
{
|
|
if((PrimarySkillValue > 100) || (!SecondaryForte))
|
|
Result = 100;
|
|
}
|
|
else if(skillid != PrimarySpecialization)
|
|
{
|
|
Result = 50;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Message(13, "Your spell casting specializations skills have been reset. "
|
|
"Only %i primary specialization skill is allowed.", MaxSpecializations);
|
|
|
|
for(int i = SPECIALIZE_ABJURE; i <= SPECIALIZE_EVOCATION; ++i)
|
|
SetSkill((SkillType)i, 1);
|
|
|
|
Save();
|
|
|
|
LogFile->write(EQEMuLog::Normal, "Reset %s's caster specialization skills to 1. "
|
|
"Too many specializations skills were above 50.", GetCleanName());
|
|
}
|
|
|
|
}
|
|
}
|
|
// This should possibly be handled by bonuses rather than here.
|
|
switch(skillid)
|
|
{
|
|
case TRACKING:
|
|
{
|
|
Result += ((GetAA(aaAdvancedTracking) * 10) + (GetAA(aaTuneofPursuance) * 10));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void Client::SetPVP(bool toggle) {
|
|
m_pp.pvp = toggle ? 1 : 0;
|
|
|
|
if(GetPVP())
|
|
this->Message_StringID(MT_Shout,PVP_ON);
|
|
else
|
|
Message(13, "You no longer follow the ways of discord.");
|
|
|
|
SendAppearancePacket(AT_PVP, GetPVP());
|
|
Save();
|
|
}
|
|
|
|
void Client::WorldKick() {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKick, sizeof(GMKick_Struct));
|
|
GMKick_Struct* gmk = (GMKick_Struct *)outapp->pBuffer;
|
|
strcpy(gmk->name,GetName());
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
Kick();
|
|
}
|
|
|
|
void Client::GMKill() {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKill, sizeof(GMKill_Struct));
|
|
GMKill_Struct* gmk = (GMKill_Struct *)outapp->pBuffer;
|
|
strcpy(gmk->name,GetName());
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
bool Client::CheckAccess(int16 iDBLevel, int16 iDefaultLevel) {
|
|
if ((admin >= iDBLevel) || (iDBLevel == 255 && admin >= iDefaultLevel))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing){
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_MemorizeSpell,sizeof(MemorizeSpell_Struct));
|
|
MemorizeSpell_Struct* mss=(MemorizeSpell_Struct*)outapp->pBuffer;
|
|
mss->scribing=scribing;
|
|
mss->slot=slot;
|
|
mss->spell_id=spellid;
|
|
outapp->priority = 5;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SetFeigned(bool in_feigned) {
|
|
if (in_feigned)
|
|
{
|
|
if(RuleB(Character, FeignKillsPet))
|
|
{
|
|
SetPet(0);
|
|
}
|
|
SetHorseId(0);
|
|
entity_list.ClearFeignAggro(this);
|
|
forget_timer.Start(FeignMemoryDuration);
|
|
} else {
|
|
forget_timer.Disable();
|
|
}
|
|
feigned=in_feigned;
|
|
}
|
|
|
|
void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const Item_Struct* item, bool buying)
|
|
{
|
|
if(!player || !merchant || !item)
|
|
return;
|
|
|
|
string LogText = "Qty: ";
|
|
|
|
char Buffer[255];
|
|
memset(Buffer, 0, sizeof(Buffer));
|
|
|
|
snprintf(Buffer, sizeof(Buffer)-1, "%3i", quantity);
|
|
LogText += Buffer;
|
|
snprintf(Buffer, sizeof(Buffer)-1, "%10i", price);
|
|
LogText += " TotalValue: ";
|
|
LogText += Buffer;
|
|
snprintf(Buffer, sizeof(Buffer)-1, " ItemID: %7i", item->ID);
|
|
LogText += Buffer;
|
|
LogText += " ";
|
|
snprintf(Buffer, sizeof(Buffer)-1, " %s", item->Name);
|
|
LogText += Buffer;
|
|
|
|
if (buying==true) {
|
|
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Buying from Merchant",LogText.c_str(),2);
|
|
}
|
|
else {
|
|
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Selling to Merchant",LogText.c_str(),3);
|
|
}
|
|
}
|
|
|
|
void Client::LogLoot(Client* player, Corpse* corpse, const Item_Struct* item){
|
|
char* logtext;
|
|
char itemid[100];
|
|
char itemname[100];
|
|
char coinloot[100];
|
|
if (item!=0){
|
|
memset(itemid,0,sizeof(itemid));
|
|
memset(itemname,0,sizeof(itemid));
|
|
itoa(item->ID,itemid,10);
|
|
sprintf(itemname,"%s",item->Name);
|
|
logtext=itemname;
|
|
|
|
strcat(logtext,"(");
|
|
strcat(logtext,itemid);
|
|
strcat(logtext,") Looted");
|
|
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Looting Item",logtext,4);
|
|
}
|
|
else{
|
|
if ((corpse->GetPlatinum() + corpse->GetGold() + corpse->GetSilver() + corpse->GetCopper())>0) {
|
|
memset(coinloot,0,sizeof(coinloot));
|
|
sprintf(coinloot,"%i PP %i GP %i SP %i CP",corpse->GetPlatinum(),corpse->GetGold(),corpse->GetSilver(),corpse->GetCopper());
|
|
logtext=coinloot;
|
|
strcat(logtext," Looted");
|
|
if (corpse->GetPlatinum()>10000)
|
|
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Excessive Loot!",logtext,9);
|
|
else
|
|
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Looting Money",logtext,5);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Client::BindWound(Mob* bindmob, bool start, bool fail){
|
|
EQApplicationPacket* outapp = 0;
|
|
if(!fail) {
|
|
outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct));
|
|
BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer;
|
|
// Start bind
|
|
if(!bindwound_timer.Enabled()) {
|
|
//make sure we actually have a bandage... and consume it.
|
|
int16 bslot = m_inv.HasItemByUse(ItemTypeBandage, 1, invWhereWorn|invWherePersonal);
|
|
if(bslot == SLOT_INVALID) {
|
|
bind_out->type = 3;
|
|
QueuePacket(outapp);
|
|
bind_out->type = 7; //this is the wrong message, dont know the right one.
|
|
QueuePacket(outapp);
|
|
return(true);
|
|
}
|
|
DeleteItemInInventory(bslot, 1, true); //do we need client update?
|
|
|
|
// start complete timer
|
|
bindwound_timer.Start(10000);
|
|
bindwound_target = bindmob;
|
|
|
|
// Send client unlock
|
|
bind_out->type = 3;
|
|
QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
// Client Unlocked
|
|
if(!bindmob) {
|
|
// send "bindmob dead" to client
|
|
bind_out->type = 4;
|
|
QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
bindwound_timer.Disable();
|
|
bindwound_target = 0;
|
|
}
|
|
else {
|
|
// send bindmob "stand still"
|
|
if(!bindmob->IsAIControlled() && bindmob != this ) {
|
|
bind_out->type = 2; // ?
|
|
//bind_out->type = 3; // ?
|
|
bind_out->to = GetID(); // ?
|
|
bindmob->CastToClient()->QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
bind_out->to = 0;
|
|
}
|
|
else if (bindmob->IsAIControlled() && bindmob != this ){
|
|
; // Tell IPC to stand still?
|
|
}
|
|
else {
|
|
; // Binding self
|
|
}
|
|
}
|
|
} else {
|
|
// finish bind
|
|
// disable complete timer
|
|
bindwound_timer.Disable();
|
|
bindwound_target = 0;
|
|
if(!bindmob){
|
|
// send "bindmob gone" to client
|
|
bind_out->type = 5; // not in zone
|
|
QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
}
|
|
|
|
else {
|
|
if (!GetFeigned() && (bindmob->DistNoRoot(*this) <= 400)) {
|
|
// send bindmob bind done
|
|
if(!bindmob->IsAIControlled() && bindmob != this ) {
|
|
|
|
}
|
|
else if(bindmob->IsAIControlled() && bindmob != this ) {
|
|
// Tell IPC to resume??
|
|
}
|
|
else {
|
|
// Binding self
|
|
}
|
|
// Send client bind done
|
|
|
|
//this is taken care of on start of bind, not finish now, and is improved
|
|
//DeleteItemInInventory(m_inv.HasItem(13009, 1), 1, true);
|
|
|
|
bind_out->type = 1; // Done
|
|
QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
CheckIncreaseSkill(BIND_WOUND, nullptr, 5);
|
|
|
|
int maxHPBonus = spellbonuses.MaxBindWound + itembonuses.MaxBindWound + aabonuses.MaxBindWound;
|
|
|
|
int max_percent = 50 + 10 * maxHPBonus;
|
|
|
|
if(GetClass() == MONK && GetSkill(BIND_WOUND) > 200) {
|
|
max_percent = 70 + 10 * maxHPBonus;
|
|
}
|
|
|
|
int max_hp = bindmob->GetMaxHP()*max_percent/100;
|
|
|
|
// send bindmob new hp's
|
|
if (bindmob->GetHP() < bindmob->GetMaxHP() && bindmob->GetHP() <= (max_hp)-1){
|
|
// 0.120 per skill point, 0.60 per skill level, minimum 3 max 30
|
|
int bindhps = 3;
|
|
|
|
|
|
if (GetSkill(BIND_WOUND) > 200) {
|
|
bindhps += GetSkill(BIND_WOUND)*4/10;
|
|
} else if (GetSkill(BIND_WOUND) >= 10) {
|
|
bindhps += GetSkill(BIND_WOUND)/4;
|
|
}
|
|
|
|
//Implementation of aaMithanielsBinding is a guess (the multiplier)
|
|
int bindBonus = spellbonuses.BindWound + itembonuses.BindWound + aabonuses.BindWound;
|
|
|
|
bindhps += bindhps*bindBonus / 100;
|
|
|
|
//if the bind takes them above the max bindable
|
|
//cap it at that value. Dont know if live does it this way
|
|
//but it makes sense to me.
|
|
int chp = bindmob->GetHP() + bindhps;
|
|
if(chp > max_hp)
|
|
chp = max_hp;
|
|
|
|
bindmob->SetHP(chp);
|
|
bindmob->SendHPUpdate();
|
|
}
|
|
else {
|
|
//I dont have the real, live
|
|
Message(15, "You cannot bind wounds above %d%% hitpoints.", max_percent);
|
|
if(bindmob->IsClient())
|
|
bindmob->CastToClient()->Message(15, "You cannot have your wounds bound above %d%% hitpoints.", max_percent);
|
|
// Too many hp message goes here.
|
|
}
|
|
}
|
|
else {
|
|
// Send client bind failed
|
|
if(bindmob != this)
|
|
bind_out->type = 6; // They moved
|
|
else
|
|
bind_out->type = 7; // Bandager moved
|
|
|
|
QueuePacket(outapp);
|
|
bind_out->type = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (bindwound_timer.Enabled()) {
|
|
// You moved
|
|
outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct));
|
|
BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer;
|
|
bindwound_timer.Disable();
|
|
bindwound_target = 0;
|
|
bind_out->type = 7;
|
|
QueuePacket(outapp);
|
|
bind_out->type = 3;
|
|
QueuePacket(outapp);
|
|
}
|
|
safe_delete(outapp);
|
|
return true;
|
|
}
|
|
|
|
void Client::SetMaterial(int16 in_slot, uint32 item_id){
|
|
const Item_Struct* item = database.GetItem(item_id);
|
|
if (item && (item->ItemClass==ItemClassCommon)) {
|
|
if (in_slot==SLOT_HEAD)
|
|
m_pp.item_material[MATERIAL_HEAD] = item->Material;
|
|
else if (in_slot==SLOT_CHEST)
|
|
m_pp.item_material[MATERIAL_CHEST] = item->Material;
|
|
else if (in_slot==SLOT_ARMS)
|
|
m_pp.item_material[MATERIAL_ARMS] = item->Material;
|
|
else if (in_slot==SLOT_BRACER01)
|
|
m_pp.item_material[MATERIAL_BRACER] = item->Material;
|
|
else if (in_slot==SLOT_BRACER02)
|
|
m_pp.item_material[MATERIAL_BRACER] = item->Material;
|
|
else if (in_slot==SLOT_HANDS)
|
|
m_pp.item_material[MATERIAL_HANDS] = item->Material;
|
|
else if (in_slot==SLOT_LEGS)
|
|
m_pp.item_material[MATERIAL_LEGS] = item->Material;
|
|
else if (in_slot==SLOT_FEET)
|
|
m_pp.item_material[MATERIAL_FEET] = item->Material;
|
|
else if (in_slot==SLOT_PRIMARY)
|
|
m_pp.item_material[MATERIAL_PRIMARY] = atoi(item->IDFile+2);
|
|
else if (in_slot==SLOT_SECONDARY)
|
|
m_pp.item_material[MATERIAL_SECONDARY] = atoi(item->IDFile+2);
|
|
}
|
|
}
|
|
|
|
void Client::ServerFilter(SetServerFilter_Struct* filter){
|
|
|
|
/* this code helps figure out the filter IDs in the packet if needed
|
|
static SetServerFilter_Struct ssss;
|
|
int r;
|
|
uint32 *o = (uint32 *) &ssss;
|
|
uint32 *n = (uint32 *) filter;
|
|
for(r = 0; r < (sizeof(SetServerFilter_Struct)/4); r++) {
|
|
if(*o != *n)
|
|
LogFile->write(EQEMuLog::Debug, "Filter %d changed from %d to %d", r, *o, *n);
|
|
o++; n++;
|
|
}
|
|
memcpy(&ssss, filter, sizeof(SetServerFilter_Struct));
|
|
*/
|
|
#define Filter0(type) \
|
|
if(filter->filters[type] == 1) \
|
|
ClientFilters[type] = FilterShow; \
|
|
else \
|
|
ClientFilters[type] = FilterHide;
|
|
#define Filter1(type) \
|
|
if(filter->filters[type] == 0) \
|
|
ClientFilters[type] = FilterShow; \
|
|
else \
|
|
ClientFilters[type] = FilterHide;
|
|
|
|
Filter0(FilterGuildChat);
|
|
Filter0(FilterSocials);
|
|
Filter0(FilterGroupChat);
|
|
Filter0(FilterShouts);
|
|
Filter0(FilterAuctions);
|
|
Filter0(FilterOOC);
|
|
Filter0(FilterBadWords);
|
|
|
|
if(filter->filters[FilterPCSpells] == 0)
|
|
ClientFilters[FilterPCSpells] = FilterShow;
|
|
else if(filter->filters[FilterPCSpells] == 1)
|
|
ClientFilters[FilterPCSpells] = FilterHide;
|
|
else
|
|
ClientFilters[FilterPCSpells] = FilterShowGroupOnly;
|
|
|
|
Filter1(FilterNPCSpells);
|
|
|
|
if(filter->filters[FilterBardSongs] == 0)
|
|
ClientFilters[FilterBardSongs] = FilterShow;
|
|
else if(filter->filters[FilterBardSongs] == 1)
|
|
ClientFilters[FilterBardSongs] = FilterShowSelfOnly;
|
|
else if(filter->filters[FilterBardSongs] == 2)
|
|
ClientFilters[FilterBardSongs] = FilterShowGroupOnly;
|
|
else
|
|
ClientFilters[FilterBardSongs] = FilterHide;
|
|
|
|
if(filter->filters[FilterSpellCrits] == 0)
|
|
ClientFilters[FilterSpellCrits] = FilterShow;
|
|
else if(filter->filters[FilterSpellCrits] == 1)
|
|
ClientFilters[FilterSpellCrits] = FilterShowSelfOnly;
|
|
else
|
|
ClientFilters[FilterSpellCrits] = FilterHide;
|
|
|
|
Filter1(FilterMeleeCrits);
|
|
|
|
if(filter->filters[FilterSpellDamage] == 0)
|
|
ClientFilters[FilterSpellDamage] = FilterShow;
|
|
else if(filter->filters[FilterSpellDamage] == 1)
|
|
ClientFilters[FilterSpellDamage] = FilterShowSelfOnly;
|
|
else
|
|
ClientFilters[FilterSpellDamage] = FilterHide;
|
|
|
|
Filter0(FilterMyMisses);
|
|
Filter0(FilterOthersMiss);
|
|
Filter0(FilterOthersHit);
|
|
Filter0(FilterMissedMe);
|
|
Filter1(FilterDamageShields);
|
|
Filter1(FilterDOT);
|
|
Filter1(FilterPetHits);
|
|
Filter1(FilterPetMisses);
|
|
Filter1(FilterFocusEffects);
|
|
Filter1(FilterPetSpells);
|
|
Filter1(FilterHealOverTime);
|
|
}
|
|
|
|
// this version is for messages with no parameters
|
|
void Client::Message_StringID(uint32 type, uint32 string_id, uint32 distance)
|
|
{
|
|
if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee)
|
|
return;
|
|
if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self...
|
|
return;
|
|
if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits)
|
|
return;
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SimpleMessage,12);
|
|
SimpleMessage_Struct* sms = (SimpleMessage_Struct*)outapp->pBuffer;
|
|
sms->color=type;
|
|
sms->string_id=string_id;
|
|
|
|
sms->unknown8=0;
|
|
|
|
if(distance>0)
|
|
entity_list.QueueCloseClients(this,outapp,false,distance);
|
|
else
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
//
|
|
// this list of 9 args isn't how I want to do it, but to use va_arg
|
|
// you have to know how many args you're expecting, and to do that we have
|
|
// to load the eqstr file and count them in the string.
|
|
// This hack sucks but it's gonna work for now.
|
|
//
|
|
void Client::Message_StringID(uint32 type, uint32 string_id, const char* message1,
|
|
const char* message2,const char* message3,const char* message4,
|
|
const char* message5,const char* message6,const char* message7,
|
|
const char* message8,const char* message9, uint32 distance)
|
|
{
|
|
if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee)
|
|
return;
|
|
if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self...
|
|
return;
|
|
if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits)
|
|
return;
|
|
if (GetFilter(FilterDamageShields) == FilterHide && type == MT_DS)
|
|
return;
|
|
|
|
int i, argcount, length;
|
|
char *bufptr;
|
|
const char *message_arg[9] = {0};
|
|
|
|
if(type==MT_Emote)
|
|
type=4;
|
|
|
|
if(!message1)
|
|
{
|
|
Message_StringID(type, string_id); // use the simple message instead
|
|
return;
|
|
}
|
|
|
|
i = 0;
|
|
message_arg[i++] = message1;
|
|
message_arg[i++] = message2;
|
|
message_arg[i++] = message3;
|
|
message_arg[i++] = message4;
|
|
message_arg[i++] = message5;
|
|
message_arg[i++] = message6;
|
|
message_arg[i++] = message7;
|
|
message_arg[i++] = message8;
|
|
message_arg[i++] = message9;
|
|
|
|
for(argcount = length = 0; message_arg[argcount]; argcount++)
|
|
length += strlen(message_arg[argcount]) + 1;
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_FormattedMessage, length+13);
|
|
FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer;
|
|
fm->string_id = string_id;
|
|
fm->type = type;
|
|
bufptr = fm->message;
|
|
for(i = 0; i < argcount; i++)
|
|
{
|
|
strcpy(bufptr, message_arg[i]);
|
|
bufptr += strlen(message_arg[i]) + 1;
|
|
}
|
|
|
|
|
|
if(distance>0)
|
|
entity_list.QueueCloseClients(this,outapp,false,distance);
|
|
else
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
|
|
void Client::SetTint(int16 in_slot, uint32 color) {
|
|
Color_Struct new_color;
|
|
new_color.color = color;
|
|
SetTint(in_slot, new_color);
|
|
}
|
|
|
|
// Still need to reconcile bracer01 versus bracer02
|
|
void Client::SetTint(int16 in_slot, Color_Struct& color) {
|
|
if (in_slot==SLOT_HEAD)
|
|
m_pp.item_tint[MATERIAL_HEAD].color=color.color;
|
|
else if (in_slot==SLOT_ARMS)
|
|
m_pp.item_tint[MATERIAL_ARMS].color=color.color;
|
|
else if (in_slot==SLOT_BRACER01)
|
|
m_pp.item_tint[MATERIAL_BRACER].color=color.color;
|
|
else if (in_slot==SLOT_BRACER02)
|
|
m_pp.item_tint[MATERIAL_BRACER].color=color.color;
|
|
else if (in_slot==SLOT_HANDS)
|
|
m_pp.item_tint[MATERIAL_HANDS].color=color.color;
|
|
else if (in_slot==SLOT_PRIMARY)
|
|
m_pp.item_tint[MATERIAL_PRIMARY].color=color.color;
|
|
else if (in_slot==SLOT_SECONDARY)
|
|
m_pp.item_tint[MATERIAL_SECONDARY].color=color.color;
|
|
else if (in_slot==SLOT_CHEST)
|
|
m_pp.item_tint[MATERIAL_CHEST].color=color.color;
|
|
else if (in_slot==SLOT_LEGS)
|
|
m_pp.item_tint[MATERIAL_LEGS].color=color.color;
|
|
else if (in_slot==SLOT_FEET)
|
|
m_pp.item_tint[MATERIAL_FEET].color=color.color;
|
|
}
|
|
|
|
void Client::SetHideMe(bool flag)
|
|
{
|
|
EQApplicationPacket app;
|
|
|
|
gmhideme = flag;
|
|
|
|
if(gmhideme)
|
|
{
|
|
database.SetHideMe(AccountID(),true);
|
|
CreateDespawnPacket(&app, false);
|
|
entity_list.RemoveFromTargets(this);
|
|
trackable = false;
|
|
}
|
|
else
|
|
{
|
|
database.SetHideMe(AccountID(),false);
|
|
CreateSpawnPacket(&app);
|
|
trackable = true;
|
|
}
|
|
|
|
entity_list.QueueClientsStatus(this, &app, true, 0, Admin()-1);
|
|
}
|
|
|
|
void Client::SetLanguageSkill(int langid, int value)
|
|
{
|
|
if (langid >= MAX_PP_LANGUAGE)
|
|
return; //Invalid Language
|
|
|
|
if (value > 100)
|
|
value = 100; //Max lang value
|
|
|
|
m_pp.languages[langid] = value;
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct));
|
|
SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer;
|
|
skill->skillId = 100 + langid;
|
|
skill->value = m_pp.languages[langid];
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify the client
|
|
}
|
|
|
|
void Client::LinkDead()
|
|
{
|
|
if (GetGroup())
|
|
{
|
|
entity_list.MessageGroup(this,true,15,"%s has gone linkdead.",GetName());
|
|
GetGroup()->DelMember(this);
|
|
}
|
|
Raid *raid = entity_list.GetRaidByClient(this);
|
|
if(raid){
|
|
raid->MemberZoned(this);
|
|
}
|
|
// save_timer.Start(2500);
|
|
linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS));
|
|
SendAppearancePacket(AT_Linkdead, 1);
|
|
client_state = CLIENT_LINKDEAD;
|
|
AI_Start(CLIENT_LD_TIMEOUT);
|
|
}
|
|
|
|
uint8 Client::SlotConvert(uint8 slot,bool bracer){
|
|
uint8 slot2=0;
|
|
if(bracer)
|
|
return SLOT_BRACER02;
|
|
switch(slot){
|
|
case MATERIAL_HEAD:
|
|
slot2=SLOT_HEAD;
|
|
break;
|
|
case MATERIAL_CHEST:
|
|
slot2=SLOT_CHEST;
|
|
break;
|
|
case MATERIAL_ARMS:
|
|
slot2=SLOT_ARMS;
|
|
break;
|
|
case MATERIAL_BRACER:
|
|
slot2=SLOT_BRACER01;
|
|
break;
|
|
case MATERIAL_HANDS:
|
|
slot2=SLOT_HANDS;
|
|
break;
|
|
case MATERIAL_LEGS:
|
|
slot2=SLOT_LEGS;
|
|
break;
|
|
case MATERIAL_FEET:
|
|
slot2=SLOT_FEET;
|
|
break;
|
|
}
|
|
return slot2;
|
|
}
|
|
|
|
uint8 Client::SlotConvert2(uint8 slot){
|
|
uint8 slot2=0;
|
|
switch(slot){
|
|
case SLOT_HEAD:
|
|
slot2=MATERIAL_HEAD;
|
|
break;
|
|
case SLOT_CHEST:
|
|
slot2=MATERIAL_CHEST;
|
|
break;
|
|
case SLOT_ARMS:
|
|
slot2=MATERIAL_ARMS;
|
|
break;
|
|
case SLOT_BRACER01:
|
|
slot2=MATERIAL_BRACER;
|
|
break;
|
|
case SLOT_HANDS:
|
|
slot2=MATERIAL_HANDS;
|
|
break;
|
|
case SLOT_LEGS:
|
|
slot2=MATERIAL_LEGS;
|
|
break;
|
|
case SLOT_FEET:
|
|
slot2=MATERIAL_FEET;
|
|
break;
|
|
}
|
|
return slot2;
|
|
}
|
|
|
|
void Client::Escape()
|
|
{
|
|
entity_list.RemoveFromTargets(this, true);
|
|
SetInvisible(1);
|
|
|
|
Message_StringID(MT_Skills, ESCAPE);
|
|
}
|
|
|
|
float Client::CalcPriceMod(Mob* other, bool reverse)
|
|
{
|
|
float chaformula = 0;
|
|
if (other)
|
|
{
|
|
int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other);
|
|
if (factionlvl >= FACTION_APPREHENSIVE) // Apprehensive or worse.
|
|
{
|
|
if (GetCHA() > 103)
|
|
{
|
|
chaformula = (GetCHA() - 103)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus.
|
|
if (chaformula < -1*(RuleI(Merchant, PriceBonusPct)))
|
|
chaformula = -1*(RuleI(Merchant, PriceBonusPct));
|
|
}
|
|
else if (GetCHA() < 103)
|
|
{
|
|
chaformula = (103 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // This will bottom out price penalty.
|
|
if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct)))
|
|
chaformula = 1*(RuleI(Merchant, PricePenaltyPct));
|
|
}
|
|
}
|
|
if (factionlvl <= FACTION_INDIFFERENT) // Indifferent or better.
|
|
{
|
|
if (GetCHA() > 75)
|
|
{
|
|
chaformula = (GetCHA() - 75)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus.
|
|
if (chaformula < -1*(RuleI(Merchant, PriceBonusPct)))
|
|
chaformula = -1*(RuleI(Merchant, PriceBonusPct));
|
|
}
|
|
else if (GetCHA() < 75)
|
|
{
|
|
chaformula = (75 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // Faction modifier keeps up from reaching bottom price penalty.
|
|
if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct)))
|
|
chaformula = 1*(RuleI(Merchant, PricePenaltyPct));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reverse)
|
|
chaformula *= -1; //For selling
|
|
//Now we have, for example, 10
|
|
chaformula /= 100; //Convert to 0.10
|
|
chaformula += 1; //Convert to 1.10;
|
|
return chaformula; //Returns 1.10, expensive stuff!
|
|
}
|
|
|
|
//neat idea from winter's roar, not implemented
|
|
void Client::Insight(uint32 t_id)
|
|
{
|
|
Mob* who = entity_list.GetMob(t_id);
|
|
if (!who)
|
|
return;
|
|
if (!who->IsNPC())
|
|
{
|
|
Message(0,"This ability can only be used on NPCs.");
|
|
return;
|
|
}
|
|
if (Dist(*who) > 200)
|
|
{
|
|
Message(0,"You must get closer to your target!");
|
|
return;
|
|
}
|
|
if (!CheckLosFN(who))
|
|
{
|
|
Message(0,"You must be able to see your target!");
|
|
return;
|
|
}
|
|
char hitpoints[64];
|
|
char resists[320];
|
|
char dmg[64];
|
|
memset(hitpoints,0,sizeof(hitpoints));
|
|
memset(resists,0,sizeof(resists));
|
|
memset(dmg,0,sizeof(dmg));
|
|
//Start with HP blah
|
|
int avg_hp = GetLevelHP(who->GetLevel());
|
|
int cur_hp = who->GetHP();
|
|
if (cur_hp == avg_hp)
|
|
{
|
|
strn0cpy(hitpoints,"averagely tough",32);
|
|
}
|
|
else if (cur_hp >= avg_hp*5)
|
|
{
|
|
strn0cpy(hitpoints,"extremely tough",32);
|
|
}
|
|
else if (cur_hp >= avg_hp*4)
|
|
{
|
|
strn0cpy(hitpoints,"exceptionally tough",32);
|
|
}
|
|
else if (cur_hp >= avg_hp*3)
|
|
{
|
|
strn0cpy(hitpoints,"very tough",32);
|
|
}
|
|
else if (cur_hp >= avg_hp*2)
|
|
{
|
|
strn0cpy(hitpoints,"quite tough",32);
|
|
}
|
|
else if (cur_hp >= avg_hp*1.25)
|
|
{
|
|
strn0cpy(hitpoints,"rather tough",32);
|
|
}
|
|
else if (cur_hp > avg_hp)
|
|
{
|
|
strn0cpy(hitpoints,"slightly tough",32);
|
|
}
|
|
else if (cur_hp <= avg_hp*0.20)
|
|
{
|
|
strn0cpy(hitpoints,"extremely frail",32);
|
|
}
|
|
else if (cur_hp <= avg_hp*0.25)
|
|
{
|
|
strn0cpy(hitpoints,"exceptionally frail",32);
|
|
}
|
|
else if (cur_hp <= avg_hp*0.33)
|
|
{
|
|
strn0cpy(hitpoints,"very frail",32);
|
|
}
|
|
else if (cur_hp <= avg_hp*0.50)
|
|
{
|
|
strn0cpy(hitpoints,"quite frail",32);
|
|
}
|
|
else if (cur_hp <= avg_hp*0.75)
|
|
{
|
|
strn0cpy(hitpoints,"rather frail",32);
|
|
}
|
|
else if (cur_hp < avg_hp)
|
|
{
|
|
strn0cpy(hitpoints,"slightly frail",32);
|
|
}
|
|
|
|
int avg_dmg = who->CastToNPC()->GetMaxDamage(who->GetLevel());
|
|
int cur_dmg = who->CastToNPC()->GetMaxDMG();
|
|
if (cur_dmg == avg_dmg)
|
|
{
|
|
strn0cpy(dmg,"averagely strong",32);
|
|
}
|
|
else if (cur_dmg >= avg_dmg*4)
|
|
{
|
|
strn0cpy(dmg,"extremely strong",32);
|
|
}
|
|
else if (cur_dmg >= avg_dmg*3)
|
|
{
|
|
strn0cpy(dmg,"exceptionally strong",32);
|
|
}
|
|
else if (cur_dmg >= avg_dmg*2)
|
|
{
|
|
strn0cpy(dmg,"very strong",32);
|
|
}
|
|
else if (cur_dmg >= avg_dmg*1.25)
|
|
{
|
|
strn0cpy(dmg,"quite strong",32);
|
|
}
|
|
else if (cur_dmg >= avg_dmg*1.10)
|
|
{
|
|
strn0cpy(dmg,"rather strong",32);
|
|
}
|
|
else if (cur_dmg > avg_dmg)
|
|
{
|
|
strn0cpy(dmg,"slightly strong",32);
|
|
}
|
|
else if (cur_dmg <= avg_dmg*0.20)
|
|
{
|
|
strn0cpy(dmg,"extremely weak",32);
|
|
}
|
|
else if (cur_dmg <= avg_dmg*0.25)
|
|
{
|
|
strn0cpy(dmg,"exceptionally weak",32);
|
|
}
|
|
else if (cur_dmg <= avg_dmg*0.33)
|
|
{
|
|
strn0cpy(dmg,"very weak",32);
|
|
}
|
|
else if (cur_dmg <= avg_dmg*0.50)
|
|
{
|
|
strn0cpy(dmg,"quite weak",32);
|
|
}
|
|
else if (cur_dmg <= avg_dmg*0.75)
|
|
{
|
|
strn0cpy(dmg,"rather weak",32);
|
|
}
|
|
else if (cur_dmg < avg_dmg)
|
|
{
|
|
strn0cpy(dmg,"slightly weak",32);
|
|
}
|
|
|
|
//Resists
|
|
int res;
|
|
int i = 1;
|
|
|
|
//MR
|
|
res = who->GetResist(i);
|
|
i++;
|
|
if (res >= 1000)
|
|
{
|
|
strcat(resists,"immune");
|
|
}
|
|
else if (res >= 500)
|
|
{
|
|
strcat(resists,"practically immune");
|
|
}
|
|
else if (res >= 250)
|
|
{
|
|
strcat(resists,"exceptionally resistant");
|
|
}
|
|
else if (res >= 150)
|
|
{
|
|
strcat(resists,"very resistant");
|
|
}
|
|
else if (res >= 100)
|
|
{
|
|
strcat(resists,"fairly resistant");
|
|
}
|
|
else if (res >= 50)
|
|
{
|
|
strcat(resists,"averagely resistant");
|
|
}
|
|
else if (res >= 25)
|
|
{
|
|
strcat(resists,"weakly resistant");
|
|
}
|
|
else
|
|
{
|
|
strcat(resists,"barely resistant");
|
|
}
|
|
strcat(resists," to magic, ");
|
|
|
|
//FR
|
|
res = who->GetResist(i);
|
|
i++;
|
|
if (res >= 1000)
|
|
{
|
|
strcat(resists,"immune");
|
|
}
|
|
else if (res >= 500)
|
|
{
|
|
strcat(resists,"practically immune");
|
|
}
|
|
else if (res >= 250)
|
|
{
|
|
strcat(resists,"exceptionally resistant");
|
|
}
|
|
else if (res >= 150)
|
|
{
|
|
strcat(resists,"very resistant");
|
|
}
|
|
else if (res >= 100)
|
|
{
|
|
strcat(resists,"fairly resistant");
|
|
}
|
|
else if (res >= 50)
|
|
{
|
|
strcat(resists,"averagely resistant");
|
|
}
|
|
else if (res >= 25)
|
|
{
|
|
strcat(resists,"weakly resistant");
|
|
}
|
|
else
|
|
{
|
|
strcat(resists,"barely resistant");
|
|
}
|
|
strcat(resists," to fire, ");
|
|
|
|
//CR
|
|
res = who->GetResist(i);
|
|
i++;
|
|
if (res >= 1000)
|
|
{
|
|
strcat(resists,"immune");
|
|
}
|
|
else if (res >= 500)
|
|
{
|
|
strcat(resists,"practically immune");
|
|
}
|
|
else if (res >= 250)
|
|
{
|
|
strcat(resists,"exceptionally resistant");
|
|
}
|
|
else if (res >= 150)
|
|
{
|
|
strcat(resists,"very resistant");
|
|
}
|
|
else if (res >= 100)
|
|
{
|
|
strcat(resists,"fairly resistant");
|
|
}
|
|
else if (res >= 50)
|
|
{
|
|
strcat(resists,"averagely resistant");
|
|
}
|
|
else if (res >= 25)
|
|
{
|
|
strcat(resists,"weakly resistant");
|
|
}
|
|
else
|
|
{
|
|
strcat(resists,"barely resistant");
|
|
}
|
|
strcat(resists," to cold, ");
|
|
|
|
//PR
|
|
res = who->GetResist(i);
|
|
i++;
|
|
if (res >= 1000)
|
|
{
|
|
strcat(resists,"immune");
|
|
}
|
|
else if (res >= 500)
|
|
{
|
|
strcat(resists,"practically immune");
|
|
}
|
|
else if (res >= 250)
|
|
{
|
|
strcat(resists,"exceptionally resistant");
|
|
}
|
|
else if (res >= 150)
|
|
{
|
|
strcat(resists,"very resistant");
|
|
}
|
|
else if (res >= 100)
|
|
{
|
|
strcat(resists,"fairly resistant");
|
|
}
|
|
else if (res >= 50)
|
|
{
|
|
strcat(resists,"averagely resistant");
|
|
}
|
|
else if (res >= 25)
|
|
{
|
|
strcat(resists,"weakly resistant");
|
|
}
|
|
else
|
|
{
|
|
strcat(resists,"barely resistant");
|
|
}
|
|
strcat(resists," to poison, and ");
|
|
|
|
//MR
|
|
res = who->GetResist(i);
|
|
i++;
|
|
if (res >= 1000)
|
|
{
|
|
strcat(resists,"immune");
|
|
}
|
|
else if (res >= 500)
|
|
{
|
|
strcat(resists,"practically immune");
|
|
}
|
|
else if (res >= 250)
|
|
{
|
|
strcat(resists,"exceptionally resistant");
|
|
}
|
|
else if (res >= 150)
|
|
{
|
|
strcat(resists,"very resistant");
|
|
}
|
|
else if (res >= 100)
|
|
{
|
|
strcat(resists,"fairly resistant");
|
|
}
|
|
else if (res >= 50)
|
|
{
|
|
strcat(resists,"averagely resistant");
|
|
}
|
|
else if (res >= 25)
|
|
{
|
|
strcat(resists,"weakly resistant");
|
|
}
|
|
else
|
|
{
|
|
strcat(resists,"barely resistant");
|
|
}
|
|
strcat(resists," to disease.");
|
|
|
|
Message(0,"Your target is a level %i %s. It appears %s and %s for its level. It seems %s",who->GetLevel(),GetEQClassName(who->GetClass(),1),dmg,hitpoints,resists);
|
|
}
|
|
|
|
void Client::ChangeSQLLog(const char *file) {
|
|
if(SQL_log != nullptr) {
|
|
fclose(SQL_log);
|
|
SQL_log = nullptr;
|
|
}
|
|
if(file != nullptr) {
|
|
if(strstr(file, "..") != nullptr) {
|
|
Message(13, ".. is forbibben in SQL log file names.");
|
|
return;
|
|
}
|
|
char buf[512];
|
|
snprintf(buf, 511, "%s%s", SQL_LOG_PATH, file);
|
|
buf[511] = '\0';
|
|
SQL_log = fopen(buf, "a");
|
|
if(SQL_log == nullptr) {
|
|
Message(13, "Unable to open SQL log file: %s\n", strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::LogSQL(const char *fmt, ...) {
|
|
if(SQL_log == nullptr)
|
|
return;
|
|
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
vfprintf(SQL_log, fmt, argptr );
|
|
fputc('\n', SQL_log);
|
|
va_end(argptr);
|
|
}
|
|
|
|
void Client::GetGroupAAs(GroupLeadershipAA_Struct *into) const {
|
|
memcpy(into, &m_pp.leader_abilities, sizeof(GroupLeadershipAA_Struct));
|
|
}
|
|
|
|
void Client::EnteringMessages(Client* client)
|
|
{
|
|
//server rules
|
|
char *rules;
|
|
rules = new char [4096];
|
|
|
|
if(database.GetVariable("Rules", rules, 4096))
|
|
{
|
|
uint8 flag = database.GetAgreementFlag(client->AccountID());
|
|
if(!flag)
|
|
{
|
|
client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)");
|
|
client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)");
|
|
client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)");
|
|
client->SendAppearancePacket(AT_Anim, ANIM_FREEZE);
|
|
}
|
|
}
|
|
safe_delete_array(rules);
|
|
}
|
|
|
|
void Client::SendRules(Client* client)
|
|
{
|
|
char *rules;
|
|
rules = new char [4096];
|
|
char *ptr;
|
|
|
|
database.GetVariable("Rules", rules, 4096);
|
|
|
|
ptr = strtok(rules, "\n");
|
|
while(ptr != nullptr)
|
|
{
|
|
|
|
client->Message(0,"%s",ptr);
|
|
ptr = strtok(nullptr, "\n");
|
|
}
|
|
safe_delete_array(rules);
|
|
}
|
|
|
|
void Client::SetEndurance(int32 newEnd)
|
|
{
|
|
/*Endurance can't be less than 0 or greater than max*/
|
|
if(newEnd < 0)
|
|
newEnd = 0;
|
|
else if(newEnd > GetMaxEndurance()){
|
|
newEnd = GetMaxEndurance();
|
|
}
|
|
|
|
cur_end = newEnd;
|
|
SendManaUpdatePacket();
|
|
}
|
|
|
|
void Client::SacrificeConfirm(Client *caster) {
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sacrifice, sizeof(Sacrifice_Struct));
|
|
Sacrifice_Struct *ss = (Sacrifice_Struct*)outapp->pBuffer;
|
|
|
|
if(!caster || PendingSacrifice) return;
|
|
|
|
if(GetLevel() < RuleI(Spells, SacrificeMinLevel)){
|
|
caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice.
|
|
return;
|
|
}
|
|
if (GetLevel() > RuleI(Spells, SacrificeMaxLevel)) {
|
|
caster->Message_StringID(13, SAC_TOO_HIGH);
|
|
return;
|
|
}
|
|
|
|
ss->CasterID = caster->GetID();
|
|
ss->TargetID = GetID();
|
|
ss->Confirm = 0;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
// We store the Caster's name, because when the packet comes back, it only has the victim's entityID in it,
|
|
// not the caster.
|
|
SacrificeCaster += caster->GetName();
|
|
PendingSacrifice = true;
|
|
}
|
|
|
|
//Essentially a special case death function
|
|
void Client::Sacrifice(Client *caster)
|
|
{
|
|
if(GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)){
|
|
int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000);
|
|
if(exploss < GetEXP()){
|
|
SetEXP(GetEXP()-exploss, GetAAXP());
|
|
SendLogoutPackets();
|
|
|
|
//make our become corpse packet, and queue to ourself before OP_Death.
|
|
EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct));
|
|
BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer;
|
|
bc->spawn_id = GetID();
|
|
bc->x = GetX();
|
|
bc->y = GetY();
|
|
bc->z = GetZ();
|
|
QueuePacket(&app2);
|
|
|
|
// make death packet
|
|
EQApplicationPacket app(OP_Death, sizeof(Death_Struct));
|
|
Death_Struct* d = (Death_Struct*)app.pBuffer;
|
|
d->spawn_id = GetID();
|
|
d->killer_id = caster ? caster->GetID() : 0;
|
|
d->bindzoneid = GetPP().binds[0].zoneId;
|
|
d->spell_id = SPELL_UNKNOWN;
|
|
d->attack_skill = 0xe7;
|
|
d->damage = 0;
|
|
app.priority = 6;
|
|
entity_list.QueueClients(this, &app);
|
|
|
|
BuffFadeAll();
|
|
UnmemSpellAll();
|
|
Group *g = GetGroup();
|
|
if(g){
|
|
g->MemberZoned(this);
|
|
}
|
|
Raid *r = entity_list.GetRaidByClient(this);
|
|
if(r){
|
|
r->MemberZoned(this);
|
|
}
|
|
ClearAllProximities();
|
|
if(RuleB(Character, LeaveCorpses)){
|
|
Corpse *new_corpse = new Corpse(this, 0);
|
|
entity_list.AddCorpse(new_corpse, GetID());
|
|
SetID(0);
|
|
entity_list.QueueClients(this, &app2, true);
|
|
}
|
|
Save();
|
|
GoToDeath();
|
|
caster->SummonItem(RuleI(Spells, SacrificeItemID));
|
|
}
|
|
}
|
|
else{
|
|
caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice.
|
|
}
|
|
}
|
|
|
|
void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) {
|
|
|
|
if(!Caster || PendingTranslocate) return;
|
|
|
|
const SPDat_Spell_Struct &Spell = spells[SpellID];
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Translocate, sizeof(Translocate_Struct));
|
|
Translocate_Struct *ts = (Translocate_Struct*)outapp->pBuffer;
|
|
|
|
strcpy(ts->Caster, Caster->GetName());
|
|
ts->SpellID = SpellID;
|
|
|
|
if((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) {
|
|
ts->ZoneID = m_pp.binds[0].zoneId;
|
|
ts->x = m_pp.binds[0].x;
|
|
ts->y = m_pp.binds[0].y;
|
|
ts->z = m_pp.binds[0].z;
|
|
}
|
|
else {
|
|
ts->ZoneID = database.GetZoneID(Spell.teleport_zone);
|
|
ts->y = Spell.base[0];
|
|
ts->x = Spell.base[1];
|
|
ts->z = Spell.base[2];
|
|
}
|
|
|
|
ts->unknown008 = 0;
|
|
ts->Complete = 0;
|
|
|
|
PendingTranslocateData = *ts;
|
|
PendingTranslocate=true;
|
|
TranslocateTime = time(nullptr);
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
return;
|
|
}
|
|
void Client::SendPickPocketResponse(Mob *from, uint32 amt, int type, const Item_Struct* item){
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct));
|
|
sPickPocket_Struct* pick_out = (sPickPocket_Struct*) outapp->pBuffer;
|
|
pick_out->coin = amt;
|
|
pick_out->from = GetID();
|
|
pick_out->to = from->GetID();
|
|
pick_out->myskill = GetSkill(PICK_POCKETS);
|
|
|
|
if((type >= PickPocketPlatinum) && (type <= PickPocketCopper) && (amt == 0))
|
|
type = PickPocketFailed;
|
|
|
|
pick_out->type = type;
|
|
if(item)
|
|
strcpy(pick_out->itemname, item->Name);
|
|
else
|
|
pick_out->itemname[0] = '\0';
|
|
//if we do not send this packet the client will lock up and require the player to relog.
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SetHoTT(uint32 mobid) {
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_TargetHoTT, sizeof(ClientTarget_Struct));
|
|
ClientTarget_Struct *ct = (ClientTarget_Struct *) outapp->pBuffer;
|
|
ct->new_target = mobid;
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendPopupToClient(const char *Title, const char *Text, uint32 PopupID, uint32 Buttons, uint32 Duration) {
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct));
|
|
OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) outapp->pBuffer;
|
|
|
|
if((strlen(Title) > (sizeof(olms->Title)-1)) ||
|
|
(strlen(Text) > (sizeof(olms->Text)-1))) return;
|
|
|
|
strcpy(olms->Title, Title);
|
|
strcpy(olms->Text, Text);
|
|
|
|
olms->Buttons = Buttons;
|
|
|
|
if(Duration > 0)
|
|
olms->Duration = Duration * 1000;
|
|
else
|
|
olms->Duration = 0xffffffff;
|
|
|
|
olms->PopupID = PopupID;
|
|
olms->NegativeID = 0;
|
|
|
|
sprintf(olms->ButtonName0, "%s", "Yes");
|
|
sprintf(olms->ButtonName1, "%s", "No");
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...) {
|
|
va_list argptr;
|
|
char buffer[4096];
|
|
|
|
va_start(argptr, Text);
|
|
vsnprintf(buffer, sizeof(buffer), Text, argptr);
|
|
va_end(argptr);
|
|
|
|
size_t len = strlen(buffer);
|
|
|
|
EQApplicationPacket* app = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct));
|
|
OnLevelMessage_Struct* olms=(OnLevelMessage_Struct*)app->pBuffer;
|
|
|
|
if(strlen(Text) > (sizeof(olms->Text)-1))
|
|
return;
|
|
|
|
if(!target)
|
|
title_type = 0;
|
|
|
|
switch (title_type)
|
|
{
|
|
case 1: {
|
|
char name[64] = "";
|
|
strcpy(name, target->GetName());
|
|
if(target->GetLastName()) {
|
|
char last_name[64] = "";
|
|
strcpy(last_name, target->GetLastName());
|
|
strcat(name, " ");
|
|
strcat(name, last_name);
|
|
}
|
|
strcpy(olms->Title, name);
|
|
break;
|
|
}
|
|
case 2: {
|
|
if(target->GuildID()) {
|
|
char *guild_name = (char*)guild_mgr.GetGuildName(target->GuildID());
|
|
strcpy(olms->Title, guild_name);
|
|
}
|
|
else {
|
|
strcpy(olms->Title, "No Guild");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
strcpy(olms->Title, Title);
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy(olms->Text, buffer, len+1);
|
|
|
|
olms->Buttons = Buttons;
|
|
|
|
sprintf(olms->ButtonName0, "%s", ButtonName0);
|
|
sprintf(olms->ButtonName1, "%s", ButtonName1);
|
|
|
|
if(Duration > 0)
|
|
olms->Duration = Duration * 1000;
|
|
else
|
|
olms->Duration = 0xffffffff;
|
|
|
|
olms->PopupID = PopupID;
|
|
olms->NegativeID = NegativeID;
|
|
|
|
FastQueuePacket(&app);
|
|
}
|
|
|
|
void Client::KeyRingLoad()
|
|
{
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char *query = 0;
|
|
MYSQL_RES *result;
|
|
MYSQL_ROW row;
|
|
query = new char[256];
|
|
|
|
sprintf(query, "SELECT item_id FROM keyring WHERE char_id='%i' ORDER BY item_id",character_id);
|
|
if (database.RunQuery(query, strlen(query), errbuf, &result))
|
|
{
|
|
safe_delete_array(query);
|
|
while(0 != (row = mysql_fetch_row(result))){
|
|
keyring.push_back(atoi(row[0]));
|
|
}
|
|
mysql_free_result(result);
|
|
}else {
|
|
cerr << "Error in Client::KeyRingLoad query '" << query << "' " << errbuf << endl;
|
|
safe_delete_array(query);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Client::KeyRingAdd(uint32 item_id)
|
|
{
|
|
if(0==item_id)return;
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char *query = 0;
|
|
uint32 affected_rows = 0;
|
|
query = new char[256];
|
|
bool bFound = KeyRingCheck(item_id);
|
|
if(!bFound){
|
|
sprintf(query, "INSERT INTO keyring(char_id,item_id) VALUES(%i,%i)",character_id,item_id);
|
|
if(database.RunQuery(query, strlen(query), errbuf, 0, &affected_rows))
|
|
{
|
|
Message(4,"Added to keyring.");
|
|
safe_delete_array(query);
|
|
}
|
|
else
|
|
{
|
|
cerr << "Error in Doors::HandleClick query '" << query << "' " << errbuf << endl;
|
|
safe_delete_array(query);
|
|
return;
|
|
}
|
|
keyring.push_back(item_id);
|
|
}
|
|
}
|
|
|
|
bool Client::KeyRingCheck(uint32 item_id)
|
|
{
|
|
for(std::list<uint32>::iterator iter = keyring.begin();
|
|
iter != keyring.end();
|
|
++iter)
|
|
{
|
|
if(*iter == item_id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::KeyRingList()
|
|
{
|
|
Message(4,"Keys on Keyring:");
|
|
const Item_Struct *item = 0;
|
|
for(std::list<uint32>::iterator iter = keyring.begin();
|
|
iter != keyring.end();
|
|
++iter)
|
|
{
|
|
if ((item = database.GetItem(*iter))!=nullptr) {
|
|
Message(4,item->Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Client::IsDiscovered(uint32 itemid) {
|
|
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char *query = 0;
|
|
MYSQL_RES *result;
|
|
MYSQL_ROW row;
|
|
|
|
if (database.RunQuery(query, MakeAnyLenString(&query, "SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid), errbuf, &result))
|
|
{
|
|
row = mysql_fetch_row(result);
|
|
if (atoi(row[0]))
|
|
{
|
|
mysql_free_result(result);
|
|
safe_delete_array(query);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cerr << "Error in IsDiscovered query '" << query << "' " << errbuf << endl;
|
|
}
|
|
mysql_free_result(result);
|
|
safe_delete_array(query);
|
|
return false;
|
|
}
|
|
|
|
void Client::DiscoverItem(uint32 itemid) {
|
|
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char* query = 0;
|
|
MYSQL_RES *result;
|
|
if (database.RunQuery(query,MakeAnyLenString(&query, "INSERT INTO discovered_items SET item_id=%lu, char_name='%s', discovered_date=UNIX_TIMESTAMP(), account_status=%i", itemid, GetName(), Admin()), errbuf, &result))
|
|
{
|
|
mysql_free_result(result);
|
|
}
|
|
safe_delete_array(query);
|
|
|
|
parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", itemid);
|
|
}
|
|
|
|
void Client::UpdateLFP() {
|
|
|
|
Group *g = GetGroup();
|
|
|
|
if(g && !g->IsLeader(this)) {
|
|
database.SetLFP(CharacterID(), false);
|
|
worldserver.StopLFP(CharacterID());
|
|
LFP = false;
|
|
return;
|
|
}
|
|
|
|
GroupLFPMemberEntry LFPMembers[MAX_GROUP_MEMBERS];
|
|
|
|
for(unsigned int i=0; i<MAX_GROUP_MEMBERS; i++) {
|
|
LFPMembers[i].Name[0] = '\0';
|
|
LFPMembers[i].Class = 0;
|
|
LFPMembers[i].Level = 0;
|
|
LFPMembers[i].Zone = 0;
|
|
}
|
|
|
|
// Slot 0 is always for the group leader, or the player if not in a group
|
|
strcpy(LFPMembers[0].Name, GetName());
|
|
LFPMembers[0].Class = GetClass();
|
|
LFPMembers[0].Level = GetLevel();
|
|
LFPMembers[0].Zone = zone->GetZoneID();
|
|
|
|
if(g) {
|
|
// Fill the LFPMembers array with the rest of the group members, excluding ourself
|
|
// We don't fill in the class, level or zone, because we may not be able to determine
|
|
// them if the other group members are not in this zone. World will fill in this information
|
|
// for us, if it can.
|
|
int NextFreeSlot = 1;
|
|
for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
|
if((g->membername[i][0] != '\0') && strcasecmp(g->membername[i], LFPMembers[0].Name))
|
|
strcpy(LFPMembers[NextFreeSlot++].Name, g->membername[i]);
|
|
}
|
|
}
|
|
worldserver.UpdateLFP(CharacterID(), LFPMembers);
|
|
}
|
|
|
|
uint16 Client::GetPrimarySkillValue()
|
|
{
|
|
SkillType skill = HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill
|
|
bool equiped = m_inv.GetItem(13);
|
|
|
|
if (!equiped)
|
|
skill = HAND_TO_HAND;
|
|
|
|
else {
|
|
|
|
uint8 type = m_inv.GetItem(13)->GetItem()->ItemType; //is this the best way to do this?
|
|
|
|
switch (type)
|
|
{
|
|
case ItemType1HS: // 1H Slashing
|
|
{
|
|
skill = _1H_SLASHING;
|
|
break;
|
|
}
|
|
case ItemType2HS: // 2H Slashing
|
|
{
|
|
skill = _2H_SLASHING;
|
|
break;
|
|
}
|
|
case ItemTypePierce: // Piercing
|
|
{
|
|
skill = PIERCING;
|
|
break;
|
|
}
|
|
case ItemType1HB: // 1H Blunt
|
|
{
|
|
skill = _1H_BLUNT;
|
|
break;
|
|
}
|
|
case ItemType2HB: // 2H Blunt
|
|
{
|
|
skill = _2H_BLUNT;
|
|
break;
|
|
}
|
|
case ItemType2HPierce: // 2H Piercing
|
|
{
|
|
skill = PIERCING;
|
|
break;
|
|
}
|
|
case ItemTypeHand2Hand: // Hand to Hand
|
|
{
|
|
skill = HAND_TO_HAND;
|
|
break;
|
|
}
|
|
default: // All other types default to Hand to Hand
|
|
{
|
|
skill = HAND_TO_HAND;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return GetSkill(skill);
|
|
}
|
|
|
|
uint16 Client::GetTotalATK()
|
|
{
|
|
uint16 AttackRating = 0;
|
|
uint16 WornCap = itembonuses.ATK;
|
|
|
|
if(IsClient()) {
|
|
AttackRating = ((WornCap * 1.342) + (GetSkill(OFFENSE) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69));
|
|
AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement();
|
|
|
|
if (AttackRating < 10)
|
|
AttackRating = 10;
|
|
}
|
|
else
|
|
AttackRating = GetATK();
|
|
|
|
AttackRating += spellbonuses.ATK;
|
|
|
|
return AttackRating;
|
|
}
|
|
|
|
uint16 Client::GetATKRating()
|
|
{
|
|
uint16 AttackRating = 0;
|
|
if(IsClient()) {
|
|
AttackRating = (GetSkill(OFFENSE) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69);
|
|
|
|
if (AttackRating < 10)
|
|
AttackRating = 10;
|
|
}
|
|
return AttackRating;
|
|
}
|
|
|
|
void Client::VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber) {
|
|
|
|
uint32 GroupOrRaidID = 0;
|
|
|
|
switch(Type) {
|
|
|
|
case VoiceMacroGroup: {
|
|
|
|
Group* g = GetGroup();
|
|
|
|
if(g)
|
|
GroupOrRaidID = g->GetID();
|
|
else
|
|
return;
|
|
|
|
break;
|
|
}
|
|
|
|
case VoiceMacroRaid: {
|
|
|
|
Raid* r = GetRaid();
|
|
|
|
if(r)
|
|
GroupOrRaidID = r->GetID();
|
|
else
|
|
return;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!worldserver.SendVoiceMacro(this, Type, Target, MacroNumber, GroupOrRaidID))
|
|
Message(0, "Error: World server disconnected");
|
|
}
|
|
|
|
void Client::ClearGroupAAs() {
|
|
|
|
for(unsigned int i = 0; i < MAX_GROUP_LEADERSHIP_AA_ARRAY; i++)
|
|
m_pp.leader_abilities.ranks[i] = 0;
|
|
|
|
m_pp.group_leadership_points = 0;
|
|
m_pp.raid_leadership_points = 0;
|
|
m_pp.group_leadership_exp = 0;
|
|
m_pp.raid_leadership_exp = 0;
|
|
|
|
Save();
|
|
}
|
|
|
|
void Client::UpdateGroupAAs(int32 points, uint32 type) {
|
|
|
|
switch(type)
|
|
{
|
|
case 0:
|
|
{
|
|
m_pp.group_leadership_points += points;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
m_pp.raid_leadership_points += points;
|
|
break;
|
|
}
|
|
}
|
|
SendLeadershipEXPUpdate();
|
|
}
|
|
|
|
bool Client::IsLeadershipEXPOn()
|
|
{
|
|
|
|
if(!m_pp.leadAAActive)
|
|
return false;
|
|
|
|
Group *g = GetGroup();
|
|
|
|
if(g && g->IsLeader(this) && (g->GroupCount() > 2))
|
|
return true;
|
|
|
|
Raid *r = GetRaid();
|
|
|
|
if(r && r->IsLeader(this) && (r->RaidCount() > 17))
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
int Client::GetAggroCount() {
|
|
return AggroCount;
|
|
}
|
|
|
|
void Client::IncrementAggroCount() {
|
|
|
|
// This method is called when a client is added to a mob's hate list. It turns the clients aggro flag on so
|
|
// rest state regen is stopped, and for SoF, it sends the opcode to show the crossed swords in-combat indicator.
|
|
//
|
|
//
|
|
AggroCount++;
|
|
|
|
if(!RuleI(Character, RestRegenPercent))
|
|
return;
|
|
|
|
// If we already had aggro before this method was called, the combat indicator should already be up for SoF clients,
|
|
// so we don't need to send it again.
|
|
//
|
|
if(AggroCount > 1)
|
|
return;
|
|
|
|
if(GetClientVersion() >= EQClientSoF) {
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 1);
|
|
char *Buffer = (char *)outapp->pBuffer;
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x01);
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
}
|
|
|
|
void Client::DecrementAggroCount() {
|
|
|
|
// This should be called when a client is removed from a mob's hate list (it dies or is memblurred).
|
|
// It checks whether any other mob is aggro on the player, and if not, starts the rest timer.
|
|
// For SoF, the opcode to start the rest state countdown timer in the UI is sent.
|
|
//
|
|
|
|
// If we didn't have aggro before, this method should not have been called.
|
|
if(!AggroCount)
|
|
return;
|
|
|
|
AggroCount--;
|
|
|
|
if(!RuleI(Character, RestRegenPercent))
|
|
return;
|
|
|
|
// Something else is still aggro on us, can't rest yet.
|
|
if(AggroCount) return;
|
|
|
|
rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000);
|
|
|
|
if(GetClientVersion() >= EQClientSoF) {
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 5);
|
|
char *Buffer = (char *)outapp->pBuffer;
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x00);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, RuleI(Character, RestRegenTimeToActivate));
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
}
|
|
|
|
void Client::SendPVPStats()
|
|
{
|
|
// This sends the data to the client to populate the PVP Stats Window.
|
|
//
|
|
// When the PVP Stats window is opened, no opcode is sent. Therefore this method should be called
|
|
// from Client::CompleteConnect, and also when the player makes a PVP kill.
|
|
//
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_PVPStats, sizeof(PVPStats_Struct));
|
|
PVPStats_Struct *pvps = (PVPStats_Struct *)outapp->pBuffer;
|
|
|
|
pvps->Kills = m_pp.PVPKills;
|
|
pvps->Deaths = m_pp.PVPDeaths;
|
|
pvps->PVPPointsAvailable = m_pp.PVPCurrentPoints;
|
|
pvps->TotalPVPPoints = m_pp.PVPCareerPoints;
|
|
pvps->BestKillStreak = m_pp.PVPBestKillStreak;
|
|
pvps->WorstDeathStreak = m_pp.PVPWorstDeathStreak;
|
|
pvps->CurrentKillStreak = m_pp.PVPCurrentKillStreak;
|
|
|
|
// TODO: Record and send other PVP Stats
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendCrystalCounts()
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_CrystalCountUpdate, sizeof(CrystalCountUpdate_Struct));
|
|
CrystalCountUpdate_Struct *ccus = (CrystalCountUpdate_Struct *)outapp->pBuffer;
|
|
|
|
ccus->CurrentRadiantCrystals = GetRadiantCrystals();
|
|
ccus->CurrentEbonCrystals = GetEbonCrystals();
|
|
ccus->CareerRadiantCrystals = m_pp.careerRadCrystals;
|
|
ccus->CareerEbonCrystals = m_pp.careerEbonCrystals;
|
|
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendDisciplineTimers()
|
|
{
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_DisciplineTimer, sizeof(DisciplineTimer_Struct));
|
|
DisciplineTimer_Struct *dts = (DisciplineTimer_Struct *)outapp->pBuffer;
|
|
|
|
for(unsigned int i = 0; i < MAX_DISCIPLINE_TIMERS; ++i)
|
|
{
|
|
uint32 RemainingTime = p_timers.GetRemainingTime(pTimerDisciplineReuseStart + i);
|
|
|
|
if(RemainingTime > 0)
|
|
{
|
|
dts->TimerID = i;
|
|
dts->Duration = RemainingTime;
|
|
QueuePacket(outapp);
|
|
}
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendRespawnBinds()
|
|
{
|
|
// This sends the data to the client to populate the Respawn from Death Window.
|
|
//
|
|
// This should be sent after OP_Death for SoF clients
|
|
// Client will respond with a 4 byte packet that includes the number of the selection made
|
|
//
|
|
|
|
|
|
const char* BindName = "Bind Location";
|
|
const char* Resurrect = "Resurrect";
|
|
|
|
int PacketLength;
|
|
|
|
PacketLength = 17 + (26 * 2) + strlen(BindName) + strlen(Resurrect); // SoF
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength);
|
|
|
|
char *Buffer = (char *)outapp->pBuffer;
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Unknown
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, RuleI(Character, RespawnFromHoverTimer) * 1000);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Unknown
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 2); // Two options, Bind or Rez
|
|
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Entry 0
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, m_pp.binds[0].zoneId);
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].x);
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].y);
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].z);
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].heading);
|
|
VARSTRUCT_ENCODE_STRING(Buffer, BindName);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0);
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // Entry 1
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, zone->GetZoneID());
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, GetX());
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, GetY());
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, GetZ());
|
|
VARSTRUCT_ENCODE_TYPE(float, Buffer, GetHeading());
|
|
VARSTRUCT_ENCODE_STRING(Buffer, Resurrect);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1);
|
|
|
|
QueuePacket(outapp);
|
|
safe_delete(outapp);
|
|
|
|
return;
|
|
}
|
|
|
|
void Client::HandleLDoNOpen(NPC *target)
|
|
{
|
|
if(target)
|
|
{
|
|
if(target->GetClass() != LDON_TREASURE)
|
|
{
|
|
LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was not a treasure chest.",
|
|
GetName(), target->GetName(), target->GetName());
|
|
return;
|
|
}
|
|
|
|
if(DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse))
|
|
{
|
|
LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was out of range",
|
|
GetName(), target->GetName(), target->GetName());
|
|
Message(13, "Treasure chest out of range.");
|
|
return;
|
|
}
|
|
|
|
if(target->IsLDoNTrapped())
|
|
{
|
|
if(target->GetLDoNTrapSpellID() != 0)
|
|
{
|
|
Message_StringID(13, LDON_ACCIDENT_SETOFF2);
|
|
target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff);
|
|
target->SetLDoNTrapSpellID(0);
|
|
target->SetLDoNTrapped(false);
|
|
target->SetLDoNTrapDetected(false);
|
|
}
|
|
else
|
|
{
|
|
target->SetLDoNTrapSpellID(0);
|
|
target->SetLDoNTrapped(false);
|
|
target->SetLDoNTrapDetected(false);
|
|
}
|
|
}
|
|
|
|
if(target->IsLDoNLocked())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_STILL_LOCKED, target->GetCleanName());
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
target->AddToHateList(this, 0, 500000, false, false, false);
|
|
if(target->GetLDoNTrapType() != 0)
|
|
{
|
|
if(GetRaid())
|
|
{
|
|
GetRaid()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target);
|
|
}
|
|
else if(GetGroup())
|
|
{
|
|
GetGroup()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target);
|
|
}
|
|
else
|
|
{
|
|
AddEXP(target->GetLevel()*target->GetLevel()*2625/10, GetLevelCon(target->GetLevel()));
|
|
}
|
|
}
|
|
target->Death(this, 1, SPELL_UNKNOWN, HAND_TO_HAND);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::HandleLDoNSenseTraps(NPC *target, uint16 skill, uint8 type)
|
|
{
|
|
if(target && target->GetClass() == LDON_TREASURE)
|
|
{
|
|
if(target->IsLDoNTrapped())
|
|
{
|
|
if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_CANT_DETERMINE_TRAP, target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if(target->IsLDoNTrapDetected())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName());
|
|
}
|
|
else
|
|
{
|
|
int check = LDoNChest_SkillCheck(target, skill);
|
|
switch(check)
|
|
{
|
|
case -1:
|
|
case 0:
|
|
Message_StringID(MT_Skills, LDON_DONT_KNOW_TRAPPED, target->GetCleanName());
|
|
break;
|
|
case 1:
|
|
Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName());
|
|
target->SetLDoNTrapDetected(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Message_StringID(MT_Skills, LDON_CERTAIN_NOT_TRAP, target->GetCleanName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::HandleLDoNDisarm(NPC *target, uint16 skill, uint8 type)
|
|
{
|
|
if(target)
|
|
{
|
|
if(target->GetClass() == LDON_TREASURE)
|
|
{
|
|
if(!target->IsLDoNTrapped())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_WAS_NOT_TRAPPED, target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
int check = 0;
|
|
if(target->IsLDoNTrapDetected())
|
|
{
|
|
check = LDoNChest_SkillCheck(target, skill);
|
|
}
|
|
else
|
|
{
|
|
check = LDoNChest_SkillCheck(target, skill*33/100);
|
|
}
|
|
switch(check)
|
|
{
|
|
case 1:
|
|
target->SetLDoNTrapDetected(false);
|
|
target->SetLDoNTrapped(false);
|
|
target->SetLDoNTrapSpellID(0);
|
|
Message_StringID(MT_Skills, LDON_HAVE_DISARMED, target->GetCleanName());
|
|
break;
|
|
case 0:
|
|
Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName());
|
|
break;
|
|
case -1:
|
|
Message_StringID(13, LDON_ACCIDENT_SETOFF2);
|
|
target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff);
|
|
target->SetLDoNTrapSpellID(0);
|
|
target->SetLDoNTrapped(false);
|
|
target->SetLDoNTrapDetected(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::HandleLDoNPickLock(NPC *target, uint16 skill, uint8 type)
|
|
{
|
|
if(target)
|
|
{
|
|
if(target->GetClass() == LDON_TREASURE)
|
|
{
|
|
if(target->IsLDoNTrapped())
|
|
{
|
|
Message_StringID(13, LDON_ACCIDENT_SETOFF2);
|
|
target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff);
|
|
target->SetLDoNTrapSpellID(0);
|
|
target->SetLDoNTrapped(false);
|
|
target->SetLDoNTrapDetected(false);
|
|
}
|
|
|
|
if(!target->IsLDoNLocked())
|
|
{
|
|
Message_StringID(MT_Skills, LDON_WAS_NOT_LOCKED, target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType())
|
|
{
|
|
Message(MT_Skills, "You cannot unlock %s with this skill.", target->GetCleanName());
|
|
return;
|
|
}
|
|
|
|
int check = LDoNChest_SkillCheck(target, skill);
|
|
|
|
switch(check)
|
|
{
|
|
case 0:
|
|
case -1:
|
|
Message_StringID(MT_Skills, LDON_PICKLOCK_FAILURE, target->GetCleanName());
|
|
break;
|
|
case 1:
|
|
target->SetLDoNLocked(false);
|
|
Message_StringID(MT_Skills, LDON_PICKLOCK_SUCCESS, target->GetCleanName());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Client::LDoNChest_SkillCheck(NPC *target, int skill)
|
|
{
|
|
if(!target)
|
|
return -1;
|
|
|
|
int chest_difficulty = target->GetLDoNLockedSkill() == 0 ? (target->GetLevel() * 5) : target->GetLDoNLockedSkill();
|
|
float base_difficulty = RuleR(Adventure, LDoNBaseTrapDifficulty);
|
|
|
|
if(chest_difficulty == 0)
|
|
chest_difficulty = 5;
|
|
|
|
float chance = ((100.0f - base_difficulty) * ((float)skill / (float)chest_difficulty));
|
|
|
|
if(chance > (100.0f - base_difficulty))
|
|
{
|
|
chance = 100.0f - base_difficulty;
|
|
}
|
|
|
|
float d100 = (float)MakeRandomFloat(0, 100);
|
|
|
|
if(d100 <= chance)
|
|
return 1;
|
|
else
|
|
{
|
|
if(d100 > (chance + RuleR(Adventure, LDoNCriticalFailTrapThreshold)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Client::SummonAndRezzAllCorpses()
|
|
{
|
|
PendingRezzXP = -1;
|
|
|
|
ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct));
|
|
|
|
ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer;
|
|
|
|
sdapcs->CharacterID = CharacterID();
|
|
sdapcs->ZoneID = zone->GetZoneID();
|
|
sdapcs->InstanceID = zone->GetInstanceID();
|
|
|
|
worldserver.SendPacket(Pack);
|
|
|
|
safe_delete(Pack);
|
|
|
|
entity_list.RemoveAllCorpsesByCharID(CharacterID());
|
|
|
|
int CorpseCount = database.SummonAllPlayerCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(),
|
|
GetX(), GetY(), GetZ(), GetHeading());
|
|
if(CorpseCount <= 0)
|
|
{
|
|
Message(clientMessageYellow, "You have no corpses to summnon.");
|
|
return;
|
|
}
|
|
|
|
int RezzExp = entity_list.RezzAllCorpsesByCharID(CharacterID());
|
|
|
|
if(RezzExp > 0)
|
|
SetEXP(GetEXP() + RezzExp, GetAAXP(), true);
|
|
|
|
Message(clientMessageYellow, "All your corpses have been summoned to your feet and have received a 100% resurrection.");
|
|
}
|
|
|
|
void Client::SummonAllCorpses(float dest_x, float dest_y, float dest_z, float dest_heading)
|
|
{
|
|
|
|
if(dest_x == 0 && dest_y == 0 && dest_z == 0 && dest_heading == 0)
|
|
{
|
|
dest_x = GetX(); dest_y = GetY(); dest_z = GetZ(); dest_heading = GetHeading();
|
|
}
|
|
|
|
ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct));
|
|
|
|
ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer;
|
|
|
|
sdapcs->CharacterID = CharacterID();
|
|
sdapcs->ZoneID = zone->GetZoneID();
|
|
sdapcs->InstanceID = zone->GetInstanceID();
|
|
|
|
worldserver.SendPacket(Pack);
|
|
|
|
safe_delete(Pack);
|
|
|
|
entity_list.RemoveAllCorpsesByCharID(CharacterID());
|
|
|
|
int CorpseCount = database.SummonAllPlayerCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(),
|
|
dest_x, dest_y, dest_z, dest_heading);
|
|
if(CorpseCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Client::DepopAllCorpses()
|
|
{
|
|
ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct));
|
|
|
|
ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer;
|
|
|
|
sdapcs->CharacterID = CharacterID();
|
|
sdapcs->ZoneID = zone->GetZoneID();
|
|
sdapcs->InstanceID = zone->GetInstanceID();
|
|
|
|
worldserver.SendPacket(Pack);
|
|
|
|
safe_delete(Pack);
|
|
|
|
entity_list.RemoveAllCorpsesByCharID(CharacterID());
|
|
}
|
|
|
|
void Client::DepopPlayerCorpse(uint32 dbid)
|
|
{
|
|
ServerPacket *Pack = new ServerPacket(ServerOP_DepopPlayerCorpse, sizeof(ServerDepopPlayerCorpse_Struct));
|
|
|
|
ServerDepopPlayerCorpse_Struct *sdpcs = (ServerDepopPlayerCorpse_Struct*)Pack->pBuffer;
|
|
|
|
sdpcs->DBID = dbid;
|
|
sdpcs->ZoneID = zone->GetZoneID();
|
|
sdpcs->InstanceID = zone->GetInstanceID();
|
|
|
|
worldserver.SendPacket(Pack);
|
|
|
|
safe_delete(Pack);
|
|
|
|
entity_list.RemoveCorpseByDBID(dbid);
|
|
}
|
|
|
|
void Client::BuryPlayerCorpses()
|
|
{
|
|
database.BuryAllPlayerCorpses(CharacterID());
|
|
}
|
|
|
|
void Client::NotifyNewTitlesAvailable()
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_NewTitlesAvailable, 0);
|
|
|
|
QueuePacket(outapp);
|
|
|
|
safe_delete(outapp);
|
|
|
|
}
|
|
|
|
void Client::SetStartZone(uint32 zoneid, float x, float y, float z)
|
|
{
|
|
// setting city to zero allows the player to use /setstartcity to set the city themselves
|
|
if(zoneid == 0) {
|
|
m_pp.binds[4].zoneId = 0;
|
|
this->Message(15,"Your starting city has been reset. Use /setstartcity to choose a new one");
|
|
return;
|
|
}
|
|
|
|
// check to make sure the zone is valid
|
|
const char *target_zone_name = database.GetZoneName(zoneid);
|
|
if(target_zone_name == nullptr)
|
|
return;
|
|
|
|
m_pp.binds[4].zoneId = zoneid;
|
|
if (x == 0 && y == 0 && z ==0)
|
|
database.GetSafePoints(m_pp.binds[4].zoneId, 0, &m_pp.binds[4].x, &m_pp.binds[4].y, &m_pp.binds[4].z);
|
|
else {
|
|
m_pp.binds[4].x = x;
|
|
m_pp.binds[4].y = y;
|
|
m_pp.binds[4].z = z;
|
|
}
|
|
}
|
|
|
|
uint32 Client::GetStartZone()
|
|
{
|
|
return m_pp.binds[4].zoneId;
|
|
}
|
|
|
|
void Client::ShowSkillsWindow()
|
|
{
|
|
const char *WindowTitle = "Skills";
|
|
string WindowText;
|
|
// using a map for easy alphabetizing of the skills list
|
|
map<string, SkillType> Skills;
|
|
map<string, SkillType>::iterator it;
|
|
|
|
// this list of names must keep the same order as that in common/skills.h
|
|
const char* SkillName[] = {"1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery",
|
|
"Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration","Defense","Disarm","Disarm Traps","Divination",
|
|
"Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand to Hand",
|
|
"Hide","Kick","Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Ripost","Round Kick","Safe Fall","Sense Heading",
|
|
"Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration","Specialize Divination","Specialize Evocation","Pick Pockets",
|
|
"Stringed Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research",
|
|
"Alchemy","Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol Tolerance","Begging","Jewelry Making",
|
|
"Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy"};
|
|
for(int i = 0; i <= (int)HIGHEST_SKILL; i++)
|
|
Skills[SkillName[i]] = (SkillType)i;
|
|
|
|
// print out all available skills
|
|
for(it = Skills.begin(); it != Skills.end(); it++) {
|
|
if(GetSkill(it->second) > 0 || MaxSkill(it->second) > 0) {
|
|
WindowText += it->first;
|
|
// line up the values
|
|
for (int j = 0; j < 5; j++)
|
|
WindowText += " ";
|
|
WindowText += itoa(this->GetSkill(it->second));
|
|
if (MaxSkill(it->second) > 0) {
|
|
WindowText += "/";
|
|
WindowText += itoa(this->GetMaxSkillAfterSpecializationRules(it->second,this->MaxSkill(it->second)));
|
|
}
|
|
WindowText += "<br>";
|
|
}
|
|
}
|
|
this->SendPopupToClient(WindowTitle, WindowText.c_str());
|
|
}
|
|
|
|
|
|
void Client::SetShadowStepExemption(bool v)
|
|
{
|
|
if(v == true)
|
|
{
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if((cur_time - m_TimeSinceLastPositionCheck) > 1000)
|
|
{
|
|
float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck);
|
|
float runs = GetRunspeed();
|
|
if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__,
|
|
m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed);
|
|
if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor)))))
|
|
{
|
|
if(IsShadowStepExempted())
|
|
{
|
|
if(m_DistanceSinceLastPositionCheck > 800)
|
|
{
|
|
CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(IsKnockBackExempted())
|
|
{
|
|
//still potential to trigger this if you're knocked back off a
|
|
//HUGE fall that takes > 2.5 seconds
|
|
if(speed > 30.0f)
|
|
{
|
|
CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(!IsPortExempted())
|
|
{
|
|
if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ()))
|
|
{
|
|
if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
CheatDetected(MQWarp, GetX(), GetY(), GetZ());
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
//Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT);
|
|
}
|
|
else
|
|
{
|
|
CheatDetected(MQWarpLight, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
}
|
|
m_ShadowStepExemption = v;
|
|
}
|
|
|
|
void Client::SetKnockBackExemption(bool v)
|
|
{
|
|
if(v == true)
|
|
{
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if((cur_time - m_TimeSinceLastPositionCheck) > 1000)
|
|
{
|
|
float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck);
|
|
float runs = GetRunspeed();
|
|
if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor)))))
|
|
{
|
|
printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__,
|
|
m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed);
|
|
if(IsShadowStepExempted())
|
|
{
|
|
if(m_DistanceSinceLastPositionCheck > 800)
|
|
{
|
|
CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(IsKnockBackExempted())
|
|
{
|
|
//still potential to trigger this if you're knocked back off a
|
|
//HUGE fall that takes > 2.5 seconds
|
|
if(speed > 30.0f)
|
|
{
|
|
CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(!IsPortExempted())
|
|
{
|
|
if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ()))
|
|
{
|
|
if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
CheatDetected(MQWarp, GetX(), GetY(), GetZ());
|
|
//Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT);
|
|
}
|
|
else
|
|
{
|
|
CheatDetected(MQWarpLight, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
}
|
|
m_KnockBackExemption = v;
|
|
}
|
|
|
|
void Client::SetPortExemption(bool v)
|
|
{
|
|
if(v == true)
|
|
{
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if((cur_time - m_TimeSinceLastPositionCheck) > 1000)
|
|
{
|
|
float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck);
|
|
float runs = GetRunspeed();
|
|
if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor)))))
|
|
{
|
|
printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__,
|
|
m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed);
|
|
if(IsShadowStepExempted())
|
|
{
|
|
if(m_DistanceSinceLastPositionCheck > 800)
|
|
{
|
|
CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(IsKnockBackExempted())
|
|
{
|
|
//still potential to trigger this if you're knocked back off a
|
|
//HUGE fall that takes > 2.5 seconds
|
|
if(speed > 30.0f)
|
|
{
|
|
CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
else if(!IsPortExempted())
|
|
{
|
|
if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ()))
|
|
{
|
|
if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
CheatDetected(MQWarp, GetX(), GetY(), GetZ());
|
|
//Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT);
|
|
}
|
|
else
|
|
{
|
|
CheatDetected(MQWarpLight, GetX(), GetY(), GetZ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_TimeSinceLastPositionCheck = cur_time;
|
|
m_DistanceSinceLastPositionCheck = 0.0f;
|
|
}
|
|
m_PortExemption = v;
|
|
}
|
|
|
|
void Client::Signal(uint32 data)
|
|
{
|
|
char buf[32];
|
|
snprintf(buf, 31, "%d", data);
|
|
buf[31] = '\0';
|
|
parse->EventPlayer(EVENT_SIGNAL, this, buf, 0);
|
|
}
|
|
|
|
const bool Client::IsMQExemptedArea(uint32 zoneID, float x, float y, float z) const
|
|
{
|
|
float max_dist = 90000;
|
|
switch(zoneID)
|
|
{
|
|
case 2:
|
|
{
|
|
float delta = (x-(-713.6));
|
|
delta *= delta;
|
|
float distance = delta;
|
|
delta = (y-(-160.2));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(-12.8));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
delta = (x-(-153.8));
|
|
delta *= delta;
|
|
distance = delta;
|
|
delta = (y-(-30.3));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(8.2));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
case 9:
|
|
{
|
|
float delta = (x-(-682.5));
|
|
delta *= delta;
|
|
float distance = delta;
|
|
delta = (y-(147.0));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(-9.9));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
delta = (x-(-655.4));
|
|
delta *= delta;
|
|
distance = delta;
|
|
delta = (y-(10.5));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(-51.8));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
case 62:
|
|
case 75:
|
|
case 114:
|
|
case 209:
|
|
{
|
|
//The portals are so common in paineel/felwitheb that checking
|
|
//distances wouldn't be worth it cause unless you're porting to the
|
|
//start field you're going to be triggering this and that's a level of
|
|
//accuracy I'm willing to sacrifice
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
case 24:
|
|
{
|
|
float delta = (x-(-183.0));
|
|
delta *= delta;
|
|
float distance = delta;
|
|
delta = (y-(-773.3));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(54.1));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
delta = (x-(-8.8));
|
|
delta *= delta;
|
|
distance = delta;
|
|
delta = (y-(-394.1));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(41.1));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
delta = (x-(-310.3));
|
|
delta *= delta;
|
|
distance = delta;
|
|
delta = (y-(-1411.6));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(-42.8));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
delta = (x-(-183.1));
|
|
delta *= delta;
|
|
distance = delta;
|
|
delta = (y-(-1409.8));
|
|
delta *= delta;
|
|
distance += delta;
|
|
delta = (z-(37.1));
|
|
delta *= delta;
|
|
distance += delta;
|
|
|
|
if(distance < max_dist)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
|
|
case 110:
|
|
case 34:
|
|
case 96:
|
|
case 93:
|
|
case 68:
|
|
case 84:
|
|
{
|
|
if(GetBoatID() != 0)
|
|
return true;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::SendRewards()
|
|
{
|
|
std::vector<ClientReward> rewards;
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char* query = 0;
|
|
MYSQL_RES *result;
|
|
MYSQL_ROW row;
|
|
|
|
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT reward_id, amount FROM"
|
|
" account_rewards WHERE account_id=%i ORDER by reward_id", AccountID()),
|
|
errbuf,&result))
|
|
{
|
|
while((row = mysql_fetch_row(result)))
|
|
{
|
|
ClientReward cr;
|
|
cr.id = atoi(row[0]);
|
|
cr.amount = atoi(row[1]);
|
|
rewards.push_back(cr);
|
|
}
|
|
mysql_free_result(result);
|
|
safe_delete_array(query);
|
|
}
|
|
else
|
|
{
|
|
LogFile->write(EQEMuLog::Error, "Error in Client::SendRewards(): %s (%s)", query, errbuf);
|
|
safe_delete_array(query);
|
|
return;
|
|
}
|
|
|
|
if(rewards.size() > 0)
|
|
{
|
|
EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetRewardsAvaliable, (sizeof(InternalVeteranReward) * rewards.size()));
|
|
uchar *data = vetapp->pBuffer;
|
|
for(int i = 0; i < rewards.size(); ++i)
|
|
{
|
|
InternalVeteranReward *ivr = (InternalVeteranReward*)data;
|
|
ivr->claim_id = rewards[i].id;
|
|
ivr->number_available = rewards[i].amount;
|
|
list<InternalVeteranReward>::iterator iter = zone->VeteranRewards.begin();
|
|
while(iter != zone->VeteranRewards.end())
|
|
{
|
|
if((*iter).claim_id == rewards[i].id)
|
|
{
|
|
break;
|
|
}
|
|
iter++;
|
|
}
|
|
|
|
if(iter != zone->VeteranRewards.end())
|
|
{
|
|
InternalVeteranReward ivro = (*iter);
|
|
ivr->claim_count = ivro.claim_count;
|
|
for(int x = 0; x < ivro.claim_count; ++x)
|
|
{
|
|
ivr->items[x].item_id = ivro.items[x].item_id;
|
|
ivr->items[x].charges = ivro.items[x].charges;
|
|
strcpy(ivr->items[x].item_name, ivro.items[x].item_name);
|
|
}
|
|
}
|
|
|
|
data += sizeof(InternalVeteranReward);
|
|
}
|
|
FastQueuePacket(&vetapp);
|
|
}
|
|
}
|
|
|
|
bool Client::TryReward(uint32 claim_id)
|
|
{
|
|
//Make sure we have an open spot
|
|
//Make sure we have it in our acct and count > 0
|
|
//Make sure the entry was found
|
|
//If we meet all the criteria:
|
|
//Decrement our count by 1 if it > 1 delete if it == 1
|
|
//Create our item in bag if necessary at the free inv slot
|
|
//save
|
|
uint32 free_slot = 0xFFFFFFFF;
|
|
|
|
for(int i = 22; i < 30; ++i)
|
|
{
|
|
ItemInst *item = GetInv().GetItem(i);
|
|
if(!item)
|
|
{
|
|
free_slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(free_slot == 0xFFFFFFFF)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
char errbuf[MYSQL_ERRMSG_SIZE];
|
|
char* query = 0;
|
|
MYSQL_RES *result;
|
|
MYSQL_ROW row;
|
|
uint32 amt = 0;
|
|
|
|
if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT amount FROM"
|
|
" account_rewards WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id),
|
|
errbuf,&result))
|
|
{
|
|
row = mysql_fetch_row(result);
|
|
if(row)
|
|
{
|
|
amt = atoi(row[0]);
|
|
}
|
|
else
|
|
{
|
|
mysql_free_result(result);
|
|
safe_delete_array(query);
|
|
return false;
|
|
}
|
|
mysql_free_result(result);
|
|
safe_delete_array(query);
|
|
}
|
|
else
|
|
{
|
|
LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf);
|
|
safe_delete_array(query);
|
|
return false;
|
|
}
|
|
|
|
if(amt == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
list<InternalVeteranReward>::iterator iter = zone->VeteranRewards.begin();
|
|
while(iter != zone->VeteranRewards.end())
|
|
{
|
|
if((*iter).claim_id == claim_id)
|
|
{
|
|
break;
|
|
}
|
|
iter++;
|
|
}
|
|
|
|
if(iter == zone->VeteranRewards.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(amt == 1)
|
|
{
|
|
if(!database.RunQuery(query,MakeAnyLenString(&query,"DELETE FROM"
|
|
" account_rewards WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id),
|
|
errbuf))
|
|
{
|
|
LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf);
|
|
safe_delete_array(query);
|
|
}
|
|
else
|
|
{
|
|
safe_delete_array(query);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!database.RunQuery(query,MakeAnyLenString(&query,"UPDATE account_rewards SET amount=(amount-1)"
|
|
" WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id),
|
|
errbuf))
|
|
{
|
|
LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf);
|
|
safe_delete_array(query);
|
|
}
|
|
else
|
|
{
|
|
safe_delete_array(query);
|
|
}
|
|
}
|
|
|
|
InternalVeteranReward ivr = (*iter);
|
|
ItemInst *claim = database.CreateItem(ivr.items[0].item_id, ivr.items[0].charges);
|
|
if(claim)
|
|
{
|
|
bool lore_conflict = false;
|
|
if(CheckLoreConflict(claim->GetItem()))
|
|
{
|
|
lore_conflict = true;
|
|
}
|
|
|
|
for(int y = 1; y < 8; y++)
|
|
{
|
|
if(ivr.items[y].item_id)
|
|
{
|
|
if(claim->GetItem()->ItemClass == 1)
|
|
{
|
|
ItemInst *item_temp = database.CreateItem(ivr.items[y].item_id, ivr.items[y].charges);
|
|
if(item_temp)
|
|
{
|
|
if(CheckLoreConflict(item_temp->GetItem()))
|
|
{
|
|
lore_conflict = true;
|
|
DuplicateLoreMessage(ivr.items[y].item_id);
|
|
}
|
|
claim->PutItem(y-1, *item_temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(lore_conflict)
|
|
{
|
|
safe_delete(claim);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PutItemInInventory(free_slot, *claim);
|
|
SendItemPacket(free_slot, claim, ItemPacketTrade);
|
|
}
|
|
}
|
|
|
|
Save();
|
|
return true;
|
|
}
|
|
|
|
uint32 Client::GetLDoNPointsTheme(uint32 t)
|
|
{
|
|
switch(t)
|
|
{
|
|
case 1:
|
|
return m_pp.ldon_points_guk;
|
|
case 2:
|
|
return m_pp.ldon_points_mir;
|
|
case 3:
|
|
return m_pp.ldon_points_mmc;
|
|
case 4:
|
|
return m_pp.ldon_points_ruj;
|
|
case 5:
|
|
return m_pp.ldon_points_tak;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32 Client::GetLDoNWinsTheme(uint32 t)
|
|
{
|
|
switch(t)
|
|
{
|
|
case 1:
|
|
return m_pp.ldon_wins_guk;
|
|
case 2:
|
|
return m_pp.ldon_wins_mir;
|
|
case 3:
|
|
return m_pp.ldon_wins_mmc;
|
|
case 4:
|
|
return m_pp.ldon_wins_ruj;
|
|
case 5:
|
|
return m_pp.ldon_wins_tak;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32 Client::GetLDoNLossesTheme(uint32 t)
|
|
{
|
|
switch(t)
|
|
{
|
|
case 1:
|
|
return m_pp.ldon_losses_guk;
|
|
case 2:
|
|
return m_pp.ldon_losses_mir;
|
|
case 3:
|
|
return m_pp.ldon_losses_mmc;
|
|
case 4:
|
|
return m_pp.ldon_losses_ruj;
|
|
case 5:
|
|
return m_pp.ldon_losses_tak;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Client::UpdateLDoNWins(uint32 t, int32 n)
|
|
{
|
|
switch(t)
|
|
{
|
|
case 1:
|
|
m_pp.ldon_wins_guk = n;
|
|
break;
|
|
case 2:
|
|
m_pp.ldon_wins_mir = n;
|
|
break;
|
|
case 3:
|
|
m_pp.ldon_wins_mmc = n;
|
|
break;
|
|
case 4:
|
|
m_pp.ldon_wins_ruj = n;
|
|
break;
|
|
case 5:
|
|
m_pp.ldon_wins_tak = n;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Client::UpdateLDoNLosses(uint32 t, int32 n)
|
|
{
|
|
switch(t)
|
|
{
|
|
case 1:
|
|
m_pp.ldon_losses_guk = n;
|
|
break;
|
|
case 2:
|
|
m_pp.ldon_losses_mir = n;
|
|
break;
|
|
case 3:
|
|
m_pp.ldon_losses_mmc = n;
|
|
break;
|
|
case 4:
|
|
m_pp.ldon_losses_ruj = n;
|
|
break;
|
|
case 5:
|
|
m_pp.ldon_losses_tak = n;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void Client::SuspendMinion()
|
|
{
|
|
NPC *CurrentPet = GetPet()->CastToNPC();
|
|
|
|
int AALevel = GetAA(aaSuspendedMinion);
|
|
|
|
if(AALevel == 0)
|
|
return;
|
|
|
|
if(GetLevel() < 62)
|
|
return;
|
|
|
|
if(!CurrentPet)
|
|
{
|
|
if(m_suspendedminion.SpellID > 0)
|
|
{
|
|
MakePoweredPet(m_suspendedminion.SpellID, spells[m_suspendedminion.SpellID].teleport_zone,
|
|
m_suspendedminion.petpower, m_suspendedminion.Name);
|
|
|
|
CurrentPet = GetPet()->CastToNPC();
|
|
|
|
if(!CurrentPet)
|
|
{
|
|
Message(13, "Failed to recall suspended minion.");
|
|
return;
|
|
}
|
|
|
|
if(AALevel >= 2)
|
|
{
|
|
CurrentPet->SetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items);
|
|
|
|
CurrentPet->SendPetBuffsToClient();
|
|
}
|
|
CurrentPet->CalcBonuses();
|
|
|
|
CurrentPet->SetHP(m_suspendedminion.HP);
|
|
|
|
CurrentPet->SetMana(m_suspendedminion.Mana);
|
|
|
|
Message_StringID(clientMessageTell, SUSPEND_MINION_UNSUSPEND, CurrentPet->GetCleanName());
|
|
|
|
memset(&m_suspendedminion, 0, sizeof(struct PetInfo));
|
|
}
|
|
else
|
|
return;
|
|
|
|
}
|
|
else
|
|
{
|
|
uint16 SpellID = CurrentPet->GetPetSpellID();
|
|
|
|
if(SpellID)
|
|
{
|
|
if(m_suspendedminion.SpellID > 0)
|
|
{
|
|
Message_StringID(clientMessageError,ONLY_ONE_PET);
|
|
|
|
return;
|
|
}
|
|
else if(CurrentPet->IsEngaged())
|
|
{
|
|
Message_StringID(clientMessageError,SUSPEND_MINION_FIGHTING);
|
|
|
|
return;
|
|
}
|
|
else if(entity_list.Fighting(CurrentPet))
|
|
{
|
|
Message_StringID(clientMessageBlue,SUSPEND_MINION_HAS_AGGRO);
|
|
}
|
|
else
|
|
{
|
|
m_suspendedminion.SpellID = SpellID;
|
|
|
|
m_suspendedminion.HP = CurrentPet->GetHP();;
|
|
|
|
m_suspendedminion.Mana = CurrentPet->GetMana();
|
|
m_suspendedminion.petpower = CurrentPet->GetPetPower();
|
|
|
|
if(AALevel >= 2)
|
|
CurrentPet->GetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items, m_suspendedminion.Name);
|
|
else
|
|
strn0cpy(m_suspendedminion.Name, CurrentPet->GetName(), 64); // Name stays even at rank 1
|
|
|
|
Message_StringID(clientMessageTell, SUSPEND_MINION_SUSPEND, CurrentPet->GetCleanName());
|
|
|
|
CurrentPet->Depop(false);
|
|
|
|
SetPetID(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Message_StringID(clientMessageError, ONLY_SUMMONED_PETS);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::AddPVPPoints(uint32 Points)
|
|
{
|
|
m_pp.PVPCurrentPoints += Points;
|
|
m_pp.PVPCareerPoints += Points;
|
|
|
|
Save();
|
|
|
|
SendPVPStats();
|
|
}
|
|
|
|
void Client::AddCrystals(uint32 Radiant, uint32 Ebon)
|
|
{
|
|
m_pp.currentRadCrystals += Radiant;
|
|
m_pp.careerRadCrystals += Radiant;
|
|
m_pp.currentEbonCrystals += Ebon;
|
|
m_pp.careerEbonCrystals += Ebon;
|
|
|
|
Save();
|
|
|
|
SendCrystalCounts();
|
|
}
|
|
|
|
// Processes a client request to inspect a SoF client's equipment.
|
|
void Client::ProcessInspectRequest(Client* requestee, Client* requester) {
|
|
if(requestee && requester) {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_InspectAnswer, sizeof(InspectResponse_Struct));
|
|
InspectResponse_Struct* insr = (InspectResponse_Struct*) outapp->pBuffer;
|
|
insr->TargetID = requester->GetID();
|
|
insr->playerid = requestee->GetID();
|
|
|
|
const Item_Struct* item = nullptr;
|
|
const ItemInst* inst = nullptr;
|
|
|
|
for(int16 L = 0; L <= 20; L++) {
|
|
inst = requestee->GetInv().GetItem(L);
|
|
|
|
if(inst) {
|
|
item = inst->GetItem();
|
|
if(item) {
|
|
strcpy(insr->itemnames[L], item->Name);
|
|
insr->itemicons[L] = item->Icon;
|
|
}
|
|
else
|
|
insr->itemicons[L] = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
inst = requestee->GetInv().GetItem(9999);
|
|
|
|
if(inst) {
|
|
item = inst->GetItem();
|
|
if(item) {
|
|
strcpy(insr->itemnames[21], item->Name);
|
|
insr->itemicons[21] = item->Icon;
|
|
}
|
|
else
|
|
insr->itemicons[21] = 0xFFFFFFFF;
|
|
}
|
|
|
|
inst = requestee->GetInv().GetItem(21);
|
|
|
|
if(inst) {
|
|
item = inst->GetItem();
|
|
if(item) {
|
|
strcpy(insr->itemnames[22], item->Name);
|
|
insr->itemicons[22] = item->Icon;
|
|
}
|
|
else
|
|
insr->itemicons[22] = 0xFFFFFFFF;
|
|
}
|
|
|
|
strcpy(insr->text, requestee->GetInspectMessage().text);
|
|
|
|
// There could be an OP for this..or not... (Ti clients are not processed here..this message is generated client-side)
|
|
if(requestee->IsClient() && (requestee != requester)) { requestee->Message(0, "%s is looking at your equipment...", requester->GetName()); }
|
|
|
|
requester->QueuePacket(outapp); // Send answer to requester
|
|
safe_delete(outapp);
|
|
}
|
|
}
|
|
|
|
void Client::GuildBankAck()
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankAck_Struct));
|
|
|
|
GuildBankAck_Struct *gbas = (GuildBankAck_Struct*) outapp->pBuffer;
|
|
|
|
gbas->Action = GuildBankAcknowledge;
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::GuildBankDepositAck(bool Fail)
|
|
{
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankDepositAck_Struct));
|
|
|
|
GuildBankDepositAck_Struct *gbdas = (GuildBankDepositAck_Struct*) outapp->pBuffer;
|
|
|
|
gbdas->Action = GuildBankDeposit;
|
|
|
|
gbdas->Fail = Fail ? 1 : 0;
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::ClearGuildBank()
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankClear_Struct));
|
|
|
|
GuildBankClear_Struct *gbcs = (GuildBankClear_Struct*) outapp->pBuffer;
|
|
|
|
gbcs->Action = GuildBankBulkItems;
|
|
gbcs->DepositAreaCount = 0;
|
|
gbcs->MainAreaCount = 0;
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendGroupCreatePacket()
|
|
{
|
|
// For SoD and later clients, this is sent the Group Leader upon initial creation of the group
|
|
//
|
|
EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupUpdateB, 32 + strlen(GetName()));
|
|
|
|
char *Buffer = (char *)outapp->pBuffer;
|
|
// Header
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // Null Leader name
|
|
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Member 0
|
|
VARSTRUCT_ENCODE_STRING(Buffer, GetName());
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, GetLevel());
|
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0);
|
|
VARSTRUCT_ENCODE_TYPE(uint16, Buffer, 0);
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendGroupLeaderChangePacket(const char *LeaderName)
|
|
{
|
|
// For SoD and later, send name of Group Leader to this client
|
|
|
|
EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupLeaderChange, sizeof(GroupLeaderChange_Struct));
|
|
|
|
GroupLeaderChange_Struct *glcs = (GroupLeaderChange_Struct*)outapp->pBuffer;
|
|
|
|
strn0cpy(glcs->LeaderName, LeaderName, sizeof(glcs->LeaderName));
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendGroupJoinAcknowledge()
|
|
{
|
|
// For SoD and later, This produces the 'You have joined the group' message.
|
|
EQApplicationPacket* outapp=new EQApplicationPacket(OP_GroupAcknowledge, 4);
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendAdventureError(const char *error)
|
|
{
|
|
size_t error_size = strlen(error);
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureInfo, (error_size + 2));
|
|
strn0cpy((char*)outapp->pBuffer, error, error_size);
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendAdventureDetails()
|
|
{
|
|
if(adv_data)
|
|
{
|
|
ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data;
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct));
|
|
AdventureRequestResponse_Struct *arr = (AdventureRequestResponse_Struct*)outapp->pBuffer;
|
|
arr->unknown000 = 0xBFC40100;
|
|
arr->unknown2080 = 0x0A;
|
|
arr->risk = ad->risk;
|
|
strcpy(arr->text, ad->text);
|
|
|
|
if(ad->time_to_enter != 0)
|
|
{
|
|
arr->timetoenter = ad->time_to_enter;
|
|
}
|
|
else
|
|
{
|
|
arr->timeleft = ad->time_left;
|
|
}
|
|
|
|
if(ad->zone_in_id == zone->GetZoneID())
|
|
{
|
|
arr->y = ad->x;
|
|
arr->x = ad->y;
|
|
arr->showcompass = 1;
|
|
}
|
|
FastQueuePacket(&outapp);
|
|
|
|
SendAdventureCount(ad->count, ad->total);
|
|
}
|
|
else
|
|
{
|
|
ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data;
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct));
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
void Client::SendAdventureCount(uint32 count, uint32 total)
|
|
{
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureUpdate, sizeof(AdventureCountUpdate_Struct));
|
|
AdventureCountUpdate_Struct *acu = (AdventureCountUpdate_Struct*)outapp->pBuffer;
|
|
acu->current = count;
|
|
acu->total = total;
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::NewAdventure(int id, int theme, const char *text, int member_count, const char *members)
|
|
{
|
|
size_t text_size = strlen(text);
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureDetails, text_size + 2);
|
|
strn0cpy((char*)outapp->pBuffer, text, text_size);
|
|
FastQueuePacket(&outapp);
|
|
|
|
adv_requested_id = id;
|
|
adv_requested_theme = theme;
|
|
safe_delete_array(adv_requested_data);
|
|
adv_requested_member_count = member_count;
|
|
adv_requested_data = new char[64 * member_count];
|
|
memcpy(adv_requested_data, members, (64 * member_count));
|
|
}
|
|
|
|
void Client::ClearPendingAdventureData()
|
|
{
|
|
adv_requested_id = 0;
|
|
adv_requested_theme = 0;
|
|
safe_delete_array(adv_requested_data);
|
|
adv_requested_member_count = 0;
|
|
}
|
|
|
|
bool Client::IsOnAdventure()
|
|
{
|
|
if(adv_data)
|
|
{
|
|
ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data;
|
|
if(ad->zone_in_id == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::LeaveAdventure()
|
|
{
|
|
if(!GetPendingAdventureLeave())
|
|
{
|
|
PendingAdventureLeave();
|
|
ServerPacket *pack = new ServerPacket(ServerOP_AdventureLeave, 64);
|
|
strcpy((char*)pack->pBuffer, GetName());
|
|
pack->Deflate();
|
|
worldserver.SendPacket(pack);
|
|
delete pack;
|
|
}
|
|
}
|
|
|
|
void Client::ClearCurrentAdventure()
|
|
{
|
|
if(adv_data)
|
|
{
|
|
ServerSendAdventureData_Struct* ds = (ServerSendAdventureData_Struct*)adv_data;
|
|
if(ds->finished_adventures > 0)
|
|
{
|
|
ds->instance_id = 0;
|
|
ds->risk = 0;
|
|
memset(ds->text, 0, 512);
|
|
ds->time_left = 0;
|
|
ds->time_to_enter = 0;
|
|
ds->x = 0;
|
|
ds->y = 0;
|
|
ds->zone_in_id = 0;
|
|
ds->zone_in_object = 0;
|
|
}
|
|
else
|
|
{
|
|
safe_delete(adv_data);
|
|
}
|
|
|
|
SendAdventureError("You are not currently assigned to an adventure.");
|
|
}
|
|
}
|
|
|
|
void Client::AdventureFinish(bool win, int theme, int points)
|
|
{
|
|
UpdateLDoNPoints(points, theme);
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureFinish, sizeof(AdventureFinish_Struct));
|
|
AdventureFinish_Struct *af = (AdventureFinish_Struct*)outapp->pBuffer;
|
|
af->win_lose = win ? 1 : 0;
|
|
af->points = points;
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::CheckLDoNHail(Mob *target)
|
|
{
|
|
if(!zone->adv_data)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!target || !target->IsNPC())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(target->GetOwnerID() != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ServerZoneAdventureDataReply_Struct* ds = (ServerZoneAdventureDataReply_Struct*)zone->adv_data;
|
|
if(ds->type != Adventure_Rescue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(ds->data_id != target->GetNPCTypeID())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(entity_list.CheckNPCsClose(target) != 0)
|
|
{
|
|
target->Say("You're here to save me? I couldn't possibly risk leaving yet. There are "
|
|
"far too many of those horrid things out there waiting to recapture me! Please get"
|
|
" rid of some more of those vermin and then we can try to leave.");
|
|
return;
|
|
}
|
|
|
|
Mob *pet = GetPet();
|
|
if(pet)
|
|
{
|
|
if(pet->GetPetType() == petCharmed)
|
|
{
|
|
pet->BuffFadeByEffect(SE_Charm);
|
|
}
|
|
else if(pet->GetPetType() == petNPCFollow)
|
|
{
|
|
pet->SetOwnerID(0);
|
|
}
|
|
else
|
|
{
|
|
pet->Depop();
|
|
}
|
|
}
|
|
|
|
SetPet(target);
|
|
target->SetOwnerID(GetID());
|
|
target->Say("Wonderful! Someone to set me free! I feared for my life for so long,"
|
|
" never knowing when they might choose to end my life. Now that you're here though"
|
|
" I can rest easy. Please help me find my way out of here as soon as you can"
|
|
" I'll stay close behind you!");
|
|
}
|
|
|
|
void Client::CheckEmoteHail(Mob *target, const char* message)
|
|
{
|
|
if(
|
|
(message[0] != 'H' &&
|
|
message[0] != 'h') ||
|
|
message[1] != 'a' ||
|
|
message[2] != 'i' ||
|
|
message[3] != 'l'){
|
|
return;
|
|
}
|
|
|
|
if(!target || !target->IsNPC())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(target->GetOwnerID() != 0)
|
|
{
|
|
return;
|
|
}
|
|
uint16 emoteid = target->GetEmoteID();
|
|
if(emoteid != 0)
|
|
target->CastToNPC()->DoNPCEmote(HAILED,emoteid);
|
|
}
|
|
|
|
void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 count)
|
|
{
|
|
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_DzCompass, sizeof(ExpeditionInfo_Struct) + sizeof(ExpeditionCompassEntry_Struct) * count);
|
|
ExpeditionCompass_Struct *ecs = (ExpeditionCompass_Struct*)outapp->pBuffer;
|
|
//ecs->clientid = GetID();
|
|
ecs->count = count;
|
|
|
|
if (count) {
|
|
ecs->entries[0].x = in_x;
|
|
ecs->entries[0].y = in_y;
|
|
ecs->entries[0].z = in_z;
|
|
}
|
|
|
|
FastQueuePacket(&outapp);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
void Client::SendZonePoints()
|
|
{
|
|
int count = 0;
|
|
LinkedListIterator<ZonePoint*> iterator(zone->zone_point_list);
|
|
iterator.Reset();
|
|
while(iterator.MoreElements())
|
|
{
|
|
ZonePoint* data = iterator.GetData();
|
|
if(GetClientVersionBit() & data->client_version_mask)
|
|
{
|
|
count++;
|
|
}
|
|
iterator.Advance();
|
|
}
|
|
|
|
uint32 zpsize = sizeof(ZonePoints) + ((count + 1) * sizeof(ZonePoint_Entry));
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendZonepoints, zpsize);
|
|
ZonePoints* zp = (ZonePoints*)outapp->pBuffer;
|
|
zp->count = count;
|
|
|
|
int i = 0;
|
|
iterator.Reset();
|
|
while(iterator.MoreElements())
|
|
{
|
|
ZonePoint* data = iterator.GetData();
|
|
if(GetClientVersionBit() & data->client_version_mask)
|
|
{
|
|
zp->zpe[i].iterator = data->number;
|
|
zp->zpe[i].x = data->target_x;
|
|
zp->zpe[i].y = data->target_y;
|
|
zp->zpe[i].z = data->target_z;
|
|
zp->zpe[i].heading = data->target_heading;
|
|
zp->zpe[i].zoneid = data->target_zone_id;
|
|
zp->zpe[i].zoneinstance = data->target_zone_instance;
|
|
i++;
|
|
}
|
|
iterator.Advance();
|
|
}
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::SendTargetCommand(uint32 EntityID)
|
|
{
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_TargetCommand, sizeof(ClientTarget_Struct));
|
|
ClientTarget_Struct *cts = (ClientTarget_Struct*)outapp->pBuffer;
|
|
cts->new_target = EntityID;
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::LocateCorpse()
|
|
{
|
|
Corpse *ClosestCorpse = nullptr;
|
|
if(!GetTarget())
|
|
ClosestCorpse = entity_list.GetClosestCorpse(this, nullptr);
|
|
else if(GetTarget()->IsCorpse())
|
|
ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->CastToCorpse()->GetOwnerName());
|
|
else
|
|
ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->GetCleanName());
|
|
|
|
if(ClosestCorpse)
|
|
{
|
|
Message_StringID(MT_Spells, SENSE_CORPSE_DIRECTION);
|
|
SetHeading(CalculateHeadingToTarget(ClosestCorpse->GetX(), ClosestCorpse->GetY()));
|
|
SetTarget(ClosestCorpse);
|
|
SendTargetCommand(ClosestCorpse->GetID());
|
|
SendPosUpdate(2);
|
|
}
|
|
else if(!GetTarget())
|
|
Message_StringID(clientMessageError, SENSE_CORPSE_NONE);
|
|
else
|
|
Message_StringID(clientMessageError, SENSE_CORPSE_NOT_NAME);
|
|
}
|
|
|
|
void Client::NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra)
|
|
{
|
|
if (!target_npc || !identifier)
|
|
return;
|
|
|
|
std::string id = identifier;
|
|
for(int i = 0; i < id.length(); ++i)
|
|
{
|
|
id[i] = tolower(id[i]);
|
|
}
|
|
|
|
if (id == "create") {
|
|
// extra tries to create the npc_type ID within the range for the current zone (zone_id * 1000)
|
|
database.NPCSpawnDB(0, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra);
|
|
}
|
|
else if (id == "add") {
|
|
// extra sets the respawn timer for add
|
|
database.NPCSpawnDB(1, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra);
|
|
}
|
|
else if (id == "update") {
|
|
database.NPCSpawnDB(2, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC());
|
|
}
|
|
else if (id == "remove") {
|
|
database.NPCSpawnDB(3, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC());
|
|
target_npc->Depop(false);
|
|
}
|
|
else if (id == "delete") {
|
|
database.NPCSpawnDB(4, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC());
|
|
target_npc->Depop(false);
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool Client::IsDraggingCorpse(const char *CorpseName)
|
|
{
|
|
for(std::list<string>::iterator Iterator = DraggedCorpses.begin(); Iterator != DraggedCorpses.end(); ++Iterator)
|
|
{
|
|
if(!strcasecmp((*Iterator).c_str(), CorpseName))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Client::DragCorpses()
|
|
{
|
|
for(std::list<string>::iterator Iterator = DraggedCorpses.begin(); Iterator != DraggedCorpses.end(); ++Iterator)
|
|
{
|
|
Mob* corpse = entity_list.GetMob((*Iterator).c_str());
|
|
|
|
if(corpse && corpse->IsPlayerCorpse() && (DistNoRootNoZ(*corpse) <= RuleR(Character, DragCorpseDistance)))
|
|
continue;
|
|
|
|
if(!corpse || !corpse->IsPlayerCorpse() || corpse->CastToCorpse()->IsBeingLooted() || !corpse->CastToCorpse()->Summon(this, false, false))
|
|
{
|
|
Message_StringID(MT_DefaultText, CORPSEDRAG_STOP);
|
|
Iterator = DraggedCorpses.erase(Iterator);
|
|
}
|
|
}
|
|
}
|
|
void Client::Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration)
|
|
{
|
|
if(!target || !IsValidSpell(spell_id) || this->GetID() == target->GetID())
|
|
return;
|
|
|
|
PetRecord record;
|
|
if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record))
|
|
{
|
|
LogFile->write(EQEMuLog::Error, "Unknown doppelganger spell id: %d, check pets table", spell_id);
|
|
Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone);
|
|
return;
|
|
}
|
|
|
|
AA_SwarmPet pet;
|
|
pet.count = pet_count;
|
|
pet.duration = pet_duration;
|
|
pet.npc_id = record.npc_type;
|
|
|
|
NPCType *made_npc = nullptr;
|
|
|
|
const NPCType *npc_type = database.GetNPCType(pet.npc_id);
|
|
if(npc_type == nullptr) {
|
|
LogFile->write(EQEMuLog::Error, "Unknown npc type for doppelganger spell id: %d", spell_id);
|
|
Message(0,"Unable to find pet!");
|
|
return;
|
|
}
|
|
// make a custom NPC type for this
|
|
made_npc = new NPCType;
|
|
memcpy(made_npc, npc_type, sizeof(NPCType));
|
|
|
|
strcpy(made_npc->name, name_override);
|
|
made_npc->level = GetLevel();
|
|
made_npc->race = GetRace();
|
|
made_npc->gender = GetGender();
|
|
made_npc->size = GetSize();
|
|
made_npc->AC = GetAC();
|
|
made_npc->STR = GetSTR();
|
|
made_npc->STA = GetSTA();
|
|
made_npc->DEX = GetDEX();
|
|
made_npc->AGI = GetAGI();
|
|
made_npc->MR = GetMR();
|
|
made_npc->FR = GetFR();
|
|
made_npc->CR = GetCR();
|
|
made_npc->DR = GetDR();
|
|
made_npc->PR = GetPR();
|
|
made_npc->Corrup = GetCorrup();
|
|
// looks
|
|
made_npc->texture = GetEquipmentMaterial(1);
|
|
made_npc->helmtexture = GetEquipmentMaterial(0);
|
|
made_npc->haircolor = GetHairColor();
|
|
made_npc->beardcolor = GetBeardColor();
|
|
made_npc->eyecolor1 = GetEyeColor1();
|
|
made_npc->eyecolor2 = GetEyeColor2();
|
|
made_npc->hairstyle = GetHairStyle();
|
|
made_npc->luclinface = GetLuclinFace();
|
|
made_npc->beard = GetBeard();
|
|
made_npc->drakkin_heritage = GetDrakkinHeritage();
|
|
made_npc->drakkin_tattoo = GetDrakkinTattoo();
|
|
made_npc->drakkin_details = GetDrakkinDetails();
|
|
made_npc->d_meele_texture1 = GetEquipmentMaterial(7);
|
|
made_npc->d_meele_texture2 = GetEquipmentMaterial(8);
|
|
for (int i = 0; i < MAX_MATERIALS; i++) {
|
|
made_npc->armor_tint[i] = GetEquipmentColor(i);
|
|
}
|
|
made_npc->loottable_id = 0;
|
|
|
|
npc_type = made_npc;
|
|
|
|
int summon_count = 0;
|
|
summon_count = pet.count;
|
|
|
|
if(summon_count > MAX_SWARM_PETS)
|
|
summon_count = MAX_SWARM_PETS;
|
|
|
|
static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, 10, -10, 10, -10, 8, -8, 8, -8 };
|
|
static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, 10, 10, -10, -10, 8, 8, -8, -8 };
|
|
TempPets(true);
|
|
|
|
while(summon_count > 0) {
|
|
NPCType *npc_dup = nullptr;
|
|
if(made_npc != nullptr) {
|
|
npc_dup = new NPCType;
|
|
memcpy(npc_dup, made_npc, sizeof(NPCType));
|
|
}
|
|
|
|
NPC* npca = new NPC(
|
|
(npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer
|
|
0,
|
|
GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count],
|
|
GetZ(), GetHeading(), FlyMode3);
|
|
|
|
if(!npca->GetSwarmInfo()){
|
|
AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo;
|
|
npca->SetSwarmInfo(nSI);
|
|
npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000);
|
|
}
|
|
else{
|
|
npca->GetSwarmInfo()->duration->Start(pet_duration*1000);
|
|
}
|
|
|
|
npca->GetSwarmInfo()->owner_id = GetID();
|
|
|
|
// Give the pets alittle more agro than the caster and then agro them on the target
|
|
target->AddToHateList(npca, (target->GetHateAmount(this) + 100), (target->GetDamageAmount(this) + 100));
|
|
npca->AddToHateList(target, 1000, 1000);
|
|
npca->GetSwarmInfo()->target = target->GetID();
|
|
|
|
//we allocated a new NPC type object, give the NPC ownership of that memory
|
|
if(npc_dup != nullptr)
|
|
npca->GiveNPCTypeData(npc_dup);
|
|
|
|
entity_list.AddNPC(npca);
|
|
summon_count--;
|
|
}
|
|
}
|
|
|
|
void Client::AssignToInstance(uint16 instance_id)
|
|
{
|
|
database.AddClientToInstance(instance_id, CharacterID());
|
|
}
|
|
|
|
void Client::SendStatsWindow(Client* client, bool use_window)
|
|
{
|
|
// Define the types of page breaks we need
|
|
std::string indP = " ";
|
|
std::string indS = " ";
|
|
std::string indM = " ";
|
|
std::string indL = " ";
|
|
std::string div = " | ";
|
|
|
|
std::string color_red = "<c \"#993333\">";
|
|
std::string color_blue = "<c \"#9999FF\">";
|
|
std::string color_green = "<c \"#33FF99\">";
|
|
std::string bright_green = "<c \"#7CFC00\">";
|
|
std::string bright_red = "<c \"#FF0000\">";
|
|
std::string heroic_color = "<c \"#d6b228\"> +";
|
|
|
|
// Set Class
|
|
std::string class_Name = itoa(GetClass());
|
|
std::string class_List[] = { "WAR", "CLR", "PAL", "RNG", "SK", "DRU", "MNK", "BRD", "ROG", "SHM", "NEC", "WIZ", "MAG", "ENC", "BST", "BER" };
|
|
|
|
if(GetClass() < 17 && GetClass() > 0) { class_Name = class_List[GetClass()-1]; }
|
|
|
|
// Race
|
|
std::string race_Name = itoa(GetRace());
|
|
switch(GetRace())
|
|
{
|
|
case 1: race_Name = "Human"; break;
|
|
case 2: race_Name = "Barbarian"; break;
|
|
case 3: race_Name = "Erudite"; break;
|
|
case 4: race_Name = "Wood Elf"; break;
|
|
case 5: race_Name = "High Elf"; break;
|
|
case 6: race_Name = "Dark Elf"; break;
|
|
case 7: race_Name = "Half Elf"; break;
|
|
case 8: race_Name = "Dwarf"; break;
|
|
case 9: race_Name = "Troll"; break;
|
|
case 10: race_Name = "Ogre"; break;
|
|
case 11: race_Name = "Halfing"; break;
|
|
case 12: race_Name = "Gnome"; break;
|
|
case 128: race_Name = "Iksar"; break;
|
|
case 130: race_Name = "Vah Shir"; break;
|
|
case 330: race_Name = "Froglok"; break;
|
|
case 522: race_Name = "Drakkin"; break;
|
|
default: break;
|
|
}
|
|
/*##########################################################
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
H/M/E String
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
##########################################################*/
|
|
std::string HME_row = "";
|
|
//Loop Variables
|
|
/*===========================*/
|
|
std::string cur_field = "";
|
|
std::string total_field = "";
|
|
std::string cur_name = "";
|
|
std::string cur_spacing = "";
|
|
std::string cur_color = "";
|
|
|
|
int hme_rows = 3; // Rows in display
|
|
int max_HME_value_len = 9; // 9 digits in the displayed value
|
|
|
|
for(int hme_row_counter = 0; hme_row_counter < hme_rows; hme_row_counter++)
|
|
{
|
|
switch(hme_row_counter) {
|
|
case 0: {
|
|
cur_name = " H: ";
|
|
cur_field = itoa(GetHP());
|
|
total_field = itoa(GetMaxHP());
|
|
break;
|
|
}
|
|
case 1: {
|
|
if(CalcMaxMana() > 0) {
|
|
cur_name = " M: ";
|
|
cur_field = itoa(GetMana());
|
|
total_field = itoa(CalcMaxMana());
|
|
}
|
|
else { continue; }
|
|
|
|
break;
|
|
}
|
|
case 2: {
|
|
cur_name = " E: ";
|
|
cur_field = itoa(GetEndurance());
|
|
total_field = itoa(GetMaxEndurance());
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
if(cur_field.compare(total_field) == 0) { cur_color = bright_green; }
|
|
else { cur_color = bright_red; }
|
|
|
|
cur_spacing.clear();
|
|
for(int a = cur_field.size(); a < max_HME_value_len; a++) { cur_spacing += " ."; }
|
|
|
|
HME_row += indM + cur_name + cur_spacing + cur_color + cur_field + "</c> / " + total_field + "<br>";
|
|
}
|
|
/*##########################################################
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
Regen String
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
##########################################################*/
|
|
std::string regen_string;
|
|
//Loop Variables
|
|
/*===========================*/
|
|
std::string regen_row_header = "";
|
|
std::string regen_row_color = "";
|
|
std::string base_regen_field = "";
|
|
std::string base_regen_spacing = "";
|
|
std::string item_regen_field = "";
|
|
std::string item_regen_spacing = "";
|
|
std::string cap_regen_field = "";
|
|
std::string cap_regen_spacing = "";
|
|
std::string spell_regen_field = "";
|
|
std::string spell_regen_spacing = "";
|
|
std::string aa_regen_field = "";
|
|
std::string aa_regen_spacing = "";
|
|
std::string total_regen_field = "";
|
|
int regen_rows = 3; // Number of rows
|
|
int max_regen_value_len = 5; // 5 digits in the displayed value(larger values will not get cut off, this is just a baseline)
|
|
|
|
for(int regen_row_counter = 0; regen_row_counter < regen_rows; regen_row_counter++)
|
|
{
|
|
switch(regen_row_counter)
|
|
{
|
|
case 0: {
|
|
regen_row_header = "H: ";
|
|
regen_row_color = color_red;
|
|
|
|
base_regen_field = itoa(LevelRegen());
|
|
item_regen_field = itoa(itembonuses.HPRegen);
|
|
cap_regen_field = itoa(CalcHPRegenCap());
|
|
spell_regen_field = itoa(spellbonuses.HPRegen);
|
|
aa_regen_field = itoa(aabonuses.HPRegen);
|
|
total_regen_field = itoa(CalcHPRegen());
|
|
break;
|
|
}
|
|
case 1: {
|
|
if(CalcMaxMana() > 0) {
|
|
regen_row_header = "M: ";
|
|
regen_row_color = color_blue;
|
|
|
|
base_regen_field = itoa(CalcBaseManaRegen());
|
|
item_regen_field = itoa(itembonuses.ManaRegen);
|
|
cap_regen_field = itoa(CalcManaRegenCap());
|
|
spell_regen_field = itoa(spellbonuses.ManaRegen);
|
|
aa_regen_field = itoa(aabonuses.ManaRegen);
|
|
total_regen_field = itoa(CalcManaRegen());
|
|
}
|
|
else { continue; }
|
|
break;
|
|
}
|
|
case 2: {
|
|
regen_row_header = "E: ";
|
|
regen_row_color = color_green;
|
|
|
|
base_regen_field = itoa(((GetLevel() * 4 / 10) + 2));
|
|
item_regen_field = itoa(itembonuses.EnduranceRegen);
|
|
cap_regen_field = itoa(CalcEnduranceRegenCap());
|
|
spell_regen_field = itoa(spellbonuses.EnduranceRegen);
|
|
aa_regen_field = itoa(aabonuses.EnduranceRegen);
|
|
total_regen_field = itoa(CalcEnduranceRegen());
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
base_regen_spacing.clear();
|
|
item_regen_spacing.clear();
|
|
cap_regen_spacing.clear();
|
|
spell_regen_spacing.clear();
|
|
aa_regen_spacing.clear();
|
|
|
|
for(int b = base_regen_field.size(); b < max_regen_value_len; b++) { base_regen_spacing += " ."; }
|
|
for(int b = item_regen_field.size(); b < max_regen_value_len; b++) { item_regen_spacing += " ."; }
|
|
for(int b = cap_regen_field.size(); b < max_regen_value_len; b++) { cap_regen_spacing += " ."; }
|
|
for(int b = spell_regen_field.size(); b < max_regen_value_len; b++) { spell_regen_spacing += " ."; }
|
|
for(int b = aa_regen_field.size(); b < max_regen_value_len; b++) { aa_regen_spacing += " ."; }
|
|
|
|
regen_string += indS + regen_row_color + regen_row_header + base_regen_spacing + base_regen_field;
|
|
regen_string += div + item_regen_spacing + item_regen_field + " (" + cap_regen_field;
|
|
regen_string += ") " + cap_regen_spacing + div + spell_regen_spacing + spell_regen_field;
|
|
regen_string += div + aa_regen_spacing + aa_regen_field + div + total_regen_field + "</c><br>";
|
|
}
|
|
/*##########################################################
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
Stat String
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
##########################################################*/
|
|
std::string stat_field = "";
|
|
//Loop Variables
|
|
/*===========================*/
|
|
//first field(stat)
|
|
std::string a_stat = "";;
|
|
std::string a_stat_name = "";
|
|
std::string a_stat_spacing = "";
|
|
//second field(heroic stat)
|
|
std::string h_stat = "";
|
|
std::string h_stat_spacing = "";
|
|
//third field(resist)
|
|
std::string a_resist = "";
|
|
std::string a_resist_name = "";
|
|
std::string a_resist_spacing = "";
|
|
//fourth field(heroic resist)
|
|
std::string h_resist_field = "";
|
|
|
|
int stat_rows = 7; // Number of rows
|
|
int max_stat_value_len = 3; // 3 digits in the displayed value
|
|
|
|
for(int stat_row_counter = 0; stat_row_counter < stat_rows; stat_row_counter++)
|
|
{
|
|
switch(stat_row_counter) {
|
|
case 0: {
|
|
a_stat_name = " STR: ";
|
|
a_resist_name = "MR: ";
|
|
a_stat = itoa(GetSTR());
|
|
h_stat = itoa(GetHeroicSTR());
|
|
a_resist = itoa(GetMR());
|
|
h_resist_field = itoa(GetHeroicMR());
|
|
break;
|
|
}
|
|
case 1: {
|
|
a_stat_name = " STA: ";
|
|
a_resist_name = "CR: ";
|
|
a_stat = itoa(GetSTA());
|
|
h_stat = itoa(GetHeroicSTA());
|
|
a_resist = itoa(GetCR());
|
|
h_resist_field = itoa(GetHeroicCR());
|
|
break;
|
|
}
|
|
case 2: {
|
|
a_stat_name = " AGI : ";
|
|
a_resist_name = "FR: ";
|
|
a_stat = itoa(GetAGI());
|
|
h_stat = itoa(GetHeroicAGI());
|
|
a_resist = itoa(GetFR());
|
|
h_resist_field = itoa(GetHeroicFR());
|
|
break;
|
|
}
|
|
case 3: {
|
|
a_stat_name = " DEX: ";
|
|
a_resist_name = "PR: ";
|
|
a_stat = itoa(GetDEX());
|
|
h_stat = itoa(GetHeroicDEX());
|
|
a_resist = itoa(GetPR());
|
|
h_resist_field = itoa(GetHeroicPR());
|
|
break;
|
|
}
|
|
case 4: {
|
|
a_stat_name = " INT : ";
|
|
a_resist_name = "DR: ";
|
|
a_stat = itoa(GetINT());
|
|
h_stat = itoa(GetHeroicINT());
|
|
a_resist = itoa(GetDR());
|
|
h_resist_field = itoa(GetHeroicDR());
|
|
break;
|
|
}
|
|
case 5: {
|
|
a_stat_name = " WIS: ";
|
|
a_resist_name = "Cp: ";
|
|
a_stat = itoa(GetWIS());
|
|
h_stat = itoa(GetHeroicWIS());
|
|
a_resist = itoa(GetCorrup());
|
|
h_resist_field = itoa(GetHeroicCorrup());
|
|
break;
|
|
}
|
|
case 6: {
|
|
a_stat_name = " CHA: ";
|
|
a_stat = itoa(GetCHA());
|
|
h_stat = itoa(GetHeroicCHA());
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
a_stat_spacing.clear();
|
|
h_stat_spacing.clear();
|
|
a_resist_spacing.clear();
|
|
|
|
for(int a = a_stat.size(); a < max_stat_value_len; a++) { a_stat_spacing += " . "; }
|
|
for(int h = h_stat.size(); h < 20; h++) { h_stat_spacing += " . "; }
|
|
for(int h = a_resist.size(); h < max_stat_value_len; h++) { a_resist_spacing += " . "; }
|
|
|
|
stat_field += indP + a_stat_name + a_stat_spacing + a_stat + heroic_color + h_stat + "</c>";
|
|
if(stat_row_counter < 6) {
|
|
stat_field += h_stat_spacing + a_resist_name + a_resist_spacing + a_resist + heroic_color + h_resist_field + "</c><br>";
|
|
}
|
|
}
|
|
/*##########################################################
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
Mod2 String
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
##########################################################*/
|
|
std::string mod2_field = "";
|
|
//Loop Variables
|
|
/*===========================*/
|
|
std::string mod2a = "";
|
|
std::string mod2a_name = "";
|
|
std::string mod2a_spacing = "";
|
|
std::string mod2a_cap = "";
|
|
std::string mod_row_spacing = "";
|
|
std::string mod2b = "";
|
|
std::string mod2b_name = "";
|
|
std::string mod2b_spacing = "";
|
|
std::string mod2b_cap = "";
|
|
int mod2a_space_count;
|
|
int mod2b_space_count;
|
|
|
|
int mod2_rows = 4;
|
|
int max_mod2_value_len = 3; // 3 digits in the displayed value
|
|
|
|
for(int mod2_row_counter = 0; mod2_row_counter < mod2_rows; mod2_row_counter++)
|
|
{
|
|
switch (mod2_row_counter)
|
|
{
|
|
case 0: {
|
|
mod2a_name = "Avoidance: ";
|
|
mod2b_name = "Combat Effects: ";
|
|
mod2a = itoa(GetAvoidance());
|
|
mod2a_cap = itoa(RuleI(Character, ItemAvoidanceCap));
|
|
mod2b = itoa(GetCombatEffects());
|
|
mod2b_cap = itoa(RuleI(Character, ItemCombatEffectsCap));
|
|
mod2a_space_count = 2;
|
|
mod2b_space_count = 0;
|
|
break;
|
|
}
|
|
case 1: {
|
|
mod2a_name = "Accuracy: ";
|
|
mod2b_name = "Strike Through: ";
|
|
mod2a = itoa(GetAccuracy());
|
|
mod2a_cap = itoa(RuleI(Character, ItemAccuracyCap));
|
|
mod2b = itoa(GetStrikeThrough());
|
|
mod2b_cap = itoa(RuleI(Character, ItemStrikethroughCap));
|
|
mod2a_space_count = 3;
|
|
mod2b_space_count = 1;
|
|
break;
|
|
}
|
|
case 2: {
|
|
mod2a_name = "Shielding: ";
|
|
mod2b_name = "Spell Shielding: ";
|
|
mod2a = itoa(GetShielding());
|
|
mod2a_cap = itoa(RuleI(Character, ItemShieldingCap));
|
|
mod2b = itoa(GetSpellShield());
|
|
mod2b_cap = itoa(RuleI(Character, ItemSpellShieldingCap));
|
|
mod2a_space_count = 2;
|
|
mod2b_space_count = 1;
|
|
break;
|
|
}
|
|
case 3: {
|
|
mod2a_name = "Stun Resist: ";
|
|
mod2b_name = "DoT Shielding: ";
|
|
mod2a = itoa(GetStunResist());
|
|
mod2a_cap = itoa(RuleI(Character, ItemStunResistCap));
|
|
mod2b = itoa(GetDoTShield());
|
|
mod2b_cap = itoa(RuleI(Character, ItemDoTShieldingCap));
|
|
mod2a_space_count = 0;
|
|
mod2b_space_count = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mod2a_spacing.clear();
|
|
mod_row_spacing.clear();
|
|
mod2b_spacing.clear();
|
|
|
|
for(int a = mod2a.size(); a < (max_mod2_value_len + mod2a_space_count); a++) { mod2a_spacing += " . "; }
|
|
for(int a = mod2a_cap.size(); a < 6 ; a++) { mod_row_spacing += " . "; }
|
|
for(int a = mod2b.size(); a < (max_mod2_value_len + mod2b_space_count); a++) { mod2b_spacing += " . "; }
|
|
|
|
mod2_field += indP + mod2a_name + mod2a_spacing + mod2a + " / " + mod2a_cap + mod_row_spacing;
|
|
mod2_field += mod2b_name + mod2b_spacing + mod2b + " / " + mod2b_cap + "<br>";
|
|
}
|
|
|
|
uint32 rune_number = 0;
|
|
uint32 magic_rune_number = 0;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (int i=0; i < buff_count; i++) {
|
|
if (buffs[i].spellid != SPELL_UNKNOWN) {
|
|
if ((HasRune() || HasPartialMeleeRune()) && buffs[i].melee_rune > 0) { rune_number += buffs[i].melee_rune; }
|
|
|
|
if ((HasSpellRune() || HasPartialSpellRune()) && buffs[i].magic_rune > 0) { magic_rune_number += buffs[i].magic_rune; }
|
|
}
|
|
}
|
|
|
|
int shield_ac = 0;
|
|
GetRawACNoShield(shield_ac);
|
|
|
|
std::string skill_list[] = {
|
|
"1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery","Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration",
|
|
"Defense","Disarm","Disarm Traps","Divination","Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand To Hand","Hide","Kick",
|
|
"Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Riposte","Round Kick","Safe Fall","Sense Heading","Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration",
|
|
"Specialize Divination","Specialize Evocation","Pick Pockets","Stringed_Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research","Alchemy",
|
|
"Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol_Tolerance","Begging","Jewelry Making","Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy"
|
|
};
|
|
|
|
std::string skill_mods = "";
|
|
for(int j = 0; j <= HIGHEST_SKILL; j++) {
|
|
if(itembonuses.skillmod[j] > 0)
|
|
skill_mods += indP + skill_list[j] + " : +" + itoa(itembonuses.skillmod[j]) + "%<br>";
|
|
else if(itembonuses.skillmod[j] < 0)
|
|
skill_mods += indP + skill_list[j] + " : -" + itoa(itembonuses.skillmod[j]) + "%<br>";
|
|
}
|
|
|
|
std::string skill_dmgs = "";
|
|
for(int j = 0; j <= HIGHEST_SKILL; j++) {
|
|
if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) > 0)
|
|
skill_dmgs += indP + skill_list[j] + " : +" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "<br>";
|
|
else if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) < 0)
|
|
skill_dmgs += indP + skill_list[j] + " : -" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "<br>";
|
|
}
|
|
|
|
std::string faction_item_string = "";
|
|
char faction_buf[256];
|
|
|
|
for(std::map <uint32, int32>::iterator iter = item_faction_bonuses.begin();
|
|
iter != item_faction_bonuses.end();
|
|
iter++)
|
|
{
|
|
memset(&faction_buf, 0, sizeof(faction_buf));
|
|
|
|
if(!database.GetFactionName((int32)((*iter).first), faction_buf, sizeof(faction_buf)))
|
|
strcpy(faction_buf, "Not in DB");
|
|
|
|
if((*iter).second > 0) {
|
|
faction_item_string += indP + faction_buf + " : +" + itoa((*iter).second) + "<br>";
|
|
}
|
|
else if((*iter).second < 0) {
|
|
faction_item_string += indP + faction_buf + " : -" + itoa((*iter).second) + "<br>";
|
|
}
|
|
}
|
|
|
|
std::string bard_info = "";
|
|
if(GetClass() == BARD) {
|
|
bard_info = indP + "Singing: " + itoa(GetSingMod()) + "<br>" +
|
|
indP + "Brass: " + itoa(GetBrassMod()) + "<br>" +
|
|
indP + "String: " + itoa(GetStringMod()) + "<br>" +
|
|
indP + "Percussion: " + itoa(GetPercMod()) + "<br>" +
|
|
indP + "Wind: " + itoa(GetWindMod()) + "<br>";
|
|
}
|
|
|
|
std::string final_stats = "" +
|
|
/* C/L/R */ indP + "Class: " + class_Name + indS + "Level: " + itoa(GetLevel()) + indS + "Race: " + race_Name + "<br>" +
|
|
/* Runes */ indP + "Rune: " + itoa(rune_number) + indL + indS + "Spell Rune: " + itoa(magic_rune_number) + "<br>" +
|
|
/* HP/M/E */ HME_row +
|
|
/* DS */ indP + "DS: " + itoa(itembonuses.DamageShield + spellbonuses.DamageShield*-1) + " (Spell: " + itoa(spellbonuses.DamageShield*-1) + " + Item: " + itoa(itembonuses.DamageShield) + " / " + itoa(RuleI(Character, ItemDamageShieldCap)) + ")<br>" +
|
|
/* Atk */ indP + "<c \"#CCFF00\">ATK: " + itoa(GetTotalATK()) + "</c><br>" +
|
|
/* Atk2 */ indP + "- Base: " + itoa(GetATKRating()) + " | Item: " + itoa(itembonuses.ATK) + " (" + itoa(RuleI(Character, ItemATKCap)) + ")~Used: " + itoa((itembonuses.ATK * 1.342)) + " | Spell: " + itoa(spellbonuses.ATK) + "<br>" +
|
|
/* AC */ indP + "<c \"#CCFF00\">AC: " + itoa(CalcAC()) + "</c><br>" +
|
|
/* AC2 */ indP + "- Mit: " + itoa(GetACMit()) + " | Avoid: " + itoa(GetACAvoid()) + " | Spell: " + itoa(spellbonuses.AC) + " | Shield: " + itoa(shield_ac) + "<br>" +
|
|
/* Haste */ indP + "<c \"#CCFF00\">Haste: " + itoa(GetHaste()) + "</c><br>" +
|
|
/* Haste2 */ indP + " - Item: " + itoa(itembonuses.haste) + " + Spell: " + itoa(spellbonuses.haste + spellbonuses.hastetype2) + " (Cap: " + itoa(RuleI(Character, HasteCap)) + ") | Over: " + itoa(spellbonuses.hastetype3 + ExtraHaste) + "<br><br>" +
|
|
/* RegenLbl */ indL + indS + "Regen<br>" + indS + indP + indP + " Base | Items (Cap) " + indP + " | Spell | A.A.s | Total<br>" +
|
|
/* Regen */ regen_string + "<br>" +
|
|
/* Stats */ stat_field + "<br><br>" +
|
|
/* Mod2s */ mod2_field + "<br>" +
|
|
/* HealAmt */ indP + "Heal Amount: " + itoa(GetHealAmt()) + " / " + itoa(RuleI(Character, ItemHealAmtCap)) + "<br>" +
|
|
/* SpellDmg*/ indP + "Spell Dmg: " + itoa(GetSpellDmg()) + " / " + itoa(RuleI(Character, ItemSpellDmgCap)) + "<br>" +
|
|
/* Clair */ indP + "Clairvoyance: " + itoa(GetClair()) + " / " + itoa(RuleI(Character, ItemClairvoyanceCap)) + "<br>" +
|
|
/* DSMit */ indP + "Dmg Shld Mit: " + itoa(GetDSMit()) + " / " + itoa(RuleI(Character, ItemDSMitigationCap)) + "<br><br>";
|
|
if(GetClass() == BARD)
|
|
final_stats += bard_info + "<br>";
|
|
if(skill_mods.size() > 0)
|
|
final_stats += skill_mods + "<br>";
|
|
if(skill_dmgs.size() > 0)
|
|
final_stats += skill_dmgs + "<br>";
|
|
if(faction_item_string.size() > 0)
|
|
final_stats += faction_item_string;
|
|
|
|
|
|
if(use_window) {
|
|
if(final_stats.size() < 4096)
|
|
{
|
|
uint32 Buttons = (client->GetClientVersion() < EQClientSoD) ? 0 : 1;
|
|
client->SendWindow(0, POPUPID_UPDATE_SHOWSTATSWINDOW, Buttons, "Cancel", "Update", 0, 1, this, "", "%s", final_stats.c_str());
|
|
goto Extra_Info;
|
|
}
|
|
else {
|
|
client->Message(15, "The window has exceeded its character limit, displaying stats to chat window:");
|
|
}
|
|
}
|
|
|
|
client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName());
|
|
client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR());
|
|
client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap());
|
|
client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac);
|
|
if(CalcMaxMana() > 0)
|
|
client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap());
|
|
client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap());
|
|
client->Message(0, " ATK: %i Worn/Spell ATK %i/%i Server Side ATK: %i", GetTotalATK(), RuleI(Character, ItemATKCap), GetATKBonus(), GetATK());
|
|
client->Message(0, " Haste: %i / %i (Item: %i + Spell: %i + Over: %i)", GetHaste(), RuleI(Character, HasteCap), itembonuses.haste, spellbonuses.haste + spellbonuses.hastetype2, spellbonuses.hastetype3 + ExtraHaste);
|
|
client->Message(0, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA());
|
|
client->Message(0, " hSTR: %i hSTA: %i hDEX: %i hAGI: %i hINT: %i hWIS: %i hCHA: %i", GetHeroicSTR(), GetHeroicSTA(), GetHeroicDEX(), GetHeroicAGI(), GetHeroicINT(), GetHeroicWIS(), GetHeroicCHA());
|
|
client->Message(0, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup());
|
|
client->Message(0, " hMR: %i hPR: %i hFR: %i hCR: %i hDR: %i hCorruption: %i", GetHeroicMR(), GetHeroicPR(), GetHeroicFR(), GetHeroicCR(), GetHeroicDR(), GetHeroicCorrup());
|
|
client->Message(0, " Shielding: %i Spell Shield: %i DoT Shielding: %i Stun Resist: %i Strikethrough: %i Avoidance: %i Accuracy: %i Combat Effects: %i", GetShielding(), GetSpellShield(), GetDoTShield(), GetStunResist(), GetStrikeThrough(), GetAvoidance(), GetAccuracy(), GetCombatEffects());
|
|
client->Message(0, " Heal Amt.: %i Spell Dmg.: %i Clairvoyance: %i DS Mitigation: %i", GetHealAmt(), GetSpellDmg(), GetClair(), GetDSMit());
|
|
if(GetClass() == BARD)
|
|
client->Message(0, " Singing: %i Brass: %i String: %i Percussion: %i Wind: %i", GetSingMod(), GetBrassMod(), GetStringMod(), GetPercMod(), GetWindMod());
|
|
|
|
Extra_Info:
|
|
|
|
client->Message(0, " BaseRace: %i Gender: %i BaseGender: %i Texture: %i HelmTexture: %i", GetBaseRace(), GetGender(), GetBaseGender(), GetTexture(), GetHelmTexture());
|
|
if (client->Admin() >= 100) {
|
|
client->Message(0, " CharID: %i EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", CharacterID(), GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted);
|
|
}
|
|
}
|
|
|
|
void Client::SendAltCurrencies() {
|
|
if(GetClientVersion() >= EQClientSoF) {
|
|
uint32 count = zone->AlternateCurrencies.size();
|
|
if(count == 0) {
|
|
return;
|
|
}
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_AltCurrency,
|
|
sizeof(AltCurrencyPopulate_Struct) + sizeof(AltCurrencyPopulateEntry_Struct) * count);
|
|
AltCurrencyPopulate_Struct *altc = (AltCurrencyPopulate_Struct*)outapp->pBuffer;
|
|
altc->opcode = ALT_CURRENCY_OP_POPULATE;
|
|
altc->count = count;
|
|
|
|
uint32 i = 0;
|
|
list<AltCurrencyDefinition_Struct>::iterator iter = zone->AlternateCurrencies.begin();
|
|
while(iter != zone->AlternateCurrencies.end()) {
|
|
const Item_Struct* item = database.GetItem((*iter).item_id);
|
|
altc->entries[i].currency_number = (*iter).id;
|
|
altc->entries[i].unknown00 = 1;
|
|
altc->entries[i].currency_number2 = (*iter).id;
|
|
altc->entries[i].item_id = (*iter).item_id;
|
|
if(item) {
|
|
altc->entries[i].item_icon = item->Icon;
|
|
altc->entries[i].stack_size = item->StackSize;
|
|
} else {
|
|
altc->entries[i].item_icon = 1000;
|
|
altc->entries[i].stack_size = 1000;
|
|
}
|
|
i++;
|
|
iter++;
|
|
}
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount)
|
|
{
|
|
alternate_currency[currency_id] = new_amount;
|
|
database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_amount);
|
|
SendAlternateCurrencyValue(currency_id);
|
|
}
|
|
|
|
void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount)
|
|
{
|
|
if(amount == 0) {
|
|
return;
|
|
}
|
|
|
|
int new_value = 0;
|
|
std::map<uint32, uint32>::iterator iter = alternate_currency.find(currency_id);
|
|
if(iter == alternate_currency.end()) {
|
|
new_value = amount;
|
|
} else {
|
|
new_value = (*iter).second + amount;
|
|
}
|
|
|
|
if(new_value < 0) {
|
|
alternate_currency[currency_id] = 0;
|
|
database.UpdateAltCurrencyValue(CharacterID(), currency_id, 0);
|
|
} else {
|
|
alternate_currency[currency_id] = new_value;
|
|
database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_value);
|
|
}
|
|
SendAlternateCurrencyValue(currency_id);
|
|
}
|
|
|
|
void Client::SendAlternateCurrencyValues()
|
|
{
|
|
list<AltCurrencyDefinition_Struct>::iterator iter = zone->AlternateCurrencies.begin();
|
|
while(iter != zone->AlternateCurrencies.end()) {
|
|
SendAlternateCurrencyValue((*iter).id, false);
|
|
iter++;
|
|
}
|
|
}
|
|
|
|
void Client::SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null)
|
|
{
|
|
uint32 value = GetAlternateCurrencyValue(currency_id);
|
|
if(value > 0 || (value == 0 && send_if_null)) {
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_AltCurrency, sizeof(AltCurrencyUpdate_Struct));
|
|
AltCurrencyUpdate_Struct *update = (AltCurrencyUpdate_Struct*)outapp->pBuffer;
|
|
update->opcode = 7;
|
|
strcpy(update->name, GetName());
|
|
update->currency_number = currency_id;
|
|
update->amount = value;
|
|
update->unknown072 = 1;
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
uint32 Client::GetAlternateCurrencyValue(uint32 currency_id) const
|
|
{
|
|
std::map<uint32, uint32>::const_iterator iter = alternate_currency.find(currency_id);
|
|
if(iter == alternate_currency.end()) {
|
|
return 0;
|
|
} else {
|
|
return (*iter).second;
|
|
}
|
|
}
|
|
|
|
void Client::OpenLFGuildWindow()
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, 8);
|
|
|
|
outapp->WriteUInt32(6);
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
bool Client::IsXTarget(const Mob *m) const
|
|
{
|
|
if(!XTargettingAvailable() || !m || (m->GetID() == 0))
|
|
return false;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if(XTargets[i].ID == m->GetID())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Client::IsClientXTarget(const Client *c) const
|
|
{
|
|
if(!XTargettingAvailable() || !c)
|
|
return false;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if(!strcasecmp(XTargets[i].Name, c->GetName()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Client::UpdateClientXTarget(Client *c)
|
|
{
|
|
if(!XTargettingAvailable() || !c)
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if(!strcasecmp(XTargets[i].Name, c->GetName()))
|
|
{
|
|
XTargets[i].ID = c->GetID();
|
|
SendXTargetPacket(i, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::AddAutoXTarget(Mob *m)
|
|
{
|
|
if(!XTargettingAvailable() || !XTargetAutoAddHaters)
|
|
return;
|
|
|
|
if(IsXTarget(m))
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0))
|
|
{
|
|
XTargets[i].ID = m->GetID();
|
|
SendXTargetPacket(i, m);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots)
|
|
{
|
|
if(!XTargettingAvailable())
|
|
return;
|
|
|
|
bool HadFreeAutoSlotsBefore = false;
|
|
|
|
int FreedAutoSlots = 0;
|
|
|
|
if(m->GetID() == 0)
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if(OnlyAutoSlots && (XTargets[i].Type !=Auto))
|
|
continue;
|
|
|
|
if(XTargets[i].ID == m->GetID())
|
|
{
|
|
if(XTargets[i].Type == CurrentTargetNPC)
|
|
XTargets[i].Type = Auto;
|
|
|
|
if(XTargets[i].Type == Auto)
|
|
++FreedAutoSlots;
|
|
|
|
XTargets[i].ID = 0;
|
|
|
|
SendXTargetPacket(i, nullptr);
|
|
}
|
|
else
|
|
{
|
|
if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0))
|
|
HadFreeAutoSlotsBefore = true;
|
|
}
|
|
}
|
|
// If there are more mobs aggro on us than we had auto-hate slots, add one of those haters into the slot(s) we just freed up.
|
|
if(!HadFreeAutoSlotsBefore && FreedAutoSlots)
|
|
entity_list.RefreshAutoXTargets(this);
|
|
}
|
|
|
|
void Client::UpdateXTargetType(XTargetType Type, Mob *m, const char *Name)
|
|
{
|
|
if(!XTargettingAvailable())
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if(XTargets[i].Type == Type)
|
|
{
|
|
if(m)
|
|
XTargets[i].ID = m->GetID();
|
|
else
|
|
XTargets[i].ID = 0;
|
|
|
|
if(Name)
|
|
strncpy(XTargets[i].Name, Name, 64);
|
|
|
|
SendXTargetPacket(i, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::SendXTargetPacket(uint32 Slot, Mob *m)
|
|
{
|
|
if(!XTargettingAvailable())
|
|
return;
|
|
|
|
uint32 PacketSize = 18;
|
|
|
|
if(m)
|
|
PacketSize += strlen(m->GetCleanName());
|
|
else
|
|
{
|
|
PacketSize += strlen(XTargets[Slot].Name);
|
|
}
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, PacketSize);
|
|
outapp->WriteUInt32(GetMaxXTargets());
|
|
outapp->WriteUInt32(1);
|
|
outapp->WriteUInt32(Slot);
|
|
if(m)
|
|
{
|
|
outapp->WriteUInt8(1);
|
|
}
|
|
else
|
|
{
|
|
if(strlen(XTargets[Slot].Name) && ((XTargets[Slot].Type == CurrentTargetPC) ||
|
|
(XTargets[Slot].Type == GroupTank) ||
|
|
(XTargets[Slot].Type == GroupAssist) ||
|
|
(XTargets[Slot].Type == Puller) ||
|
|
(XTargets[Slot].Type == RaidAssist1) ||
|
|
(XTargets[Slot].Type == RaidAssist2) ||
|
|
(XTargets[Slot].Type == RaidAssist3)))
|
|
{
|
|
outapp->WriteUInt8(2);
|
|
}
|
|
else
|
|
{
|
|
outapp->WriteUInt8(0);
|
|
}
|
|
}
|
|
outapp->WriteUInt32(XTargets[Slot].ID);
|
|
outapp->WriteString(m ? m->GetCleanName() : XTargets[Slot].Name);
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
void Client::RemoveGroupXTargets()
|
|
{
|
|
if(!XTargettingAvailable())
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
{
|
|
if((XTargets[i].Type == GroupTank) ||
|
|
(XTargets[i].Type == GroupAssist) ||
|
|
(XTargets[i].Type == Puller) ||
|
|
(XTargets[i].Type == RaidAssist1) ||
|
|
(XTargets[i].Type == RaidAssist2) ||
|
|
(XTargets[i].Type == RaidAssist3) ||
|
|
(XTargets[i].Type == GroupMarkTarget1) ||
|
|
(XTargets[i].Type == GroupMarkTarget2) ||
|
|
(XTargets[i].Type == GroupMarkTarget3))
|
|
{
|
|
XTargets[i].ID = 0;
|
|
XTargets[i].Name[0] = 0;
|
|
SendXTargetPacket(i, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::ShowXTargets(Client *c)
|
|
{
|
|
if(!c)
|
|
return;
|
|
|
|
for(int i = 0; i < GetMaxXTargets(); ++i)
|
|
c->Message(0, "Xtarget Slot: %i, Type: %2i, ID: %4i, Name: %s", i, XTargets[i].Type, XTargets[i].ID, XTargets[i].Name);
|
|
}
|
|
|
|
void Client::SetMaxXTargets(uint8 NewMax)
|
|
{
|
|
if(!XTargettingAvailable())
|
|
return;
|
|
|
|
if(NewMax > XTARGET_HARDCAP)
|
|
return;
|
|
|
|
MaxXTargets = NewMax;
|
|
|
|
Save(0);
|
|
|
|
for(int i = MaxXTargets; i < XTARGET_HARDCAP; ++i)
|
|
{
|
|
XTargets[i].Type = Auto;
|
|
XTargets[i].ID = 0;
|
|
XTargets[i].Name[0] = 0;
|
|
}
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, 8);
|
|
outapp->WriteUInt32(GetMaxXTargets());
|
|
outapp->WriteUInt32(0);
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
const char* Client::GetRacePlural(Client* client) {
|
|
|
|
switch (client->CastToMob()->GetRace()) {
|
|
case HUMAN:
|
|
return "Humans"; break;
|
|
case BARBARIAN:
|
|
return "Barbarians"; break;
|
|
case ERUDITE:
|
|
return "Erudites"; break;
|
|
case WOOD_ELF:
|
|
return "Wood Elves"; break;
|
|
case HIGH_ELF:
|
|
return "High Elves"; break;
|
|
case DARK_ELF:
|
|
return "Dark Elves"; break;
|
|
case HALF_ELF:
|
|
return "Half Elves"; break;
|
|
case DWARF:
|
|
return "Dwarves"; break;
|
|
case TROLL:
|
|
return "Trolls"; break;
|
|
case OGRE:
|
|
return "Ogres"; break;
|
|
case HALFLING:
|
|
return "Halflings"; break;
|
|
case GNOME:
|
|
return "Gnomes"; break;
|
|
case IKSAR:
|
|
return "Iksar"; break;
|
|
case VAHSHIR:
|
|
return "Vah Shir"; break;
|
|
case FROGLOK:
|
|
return "Frogloks"; break;
|
|
case DRAKKIN:
|
|
return "Drakkin"; break;
|
|
default:
|
|
return "Races"; break;
|
|
}
|
|
}
|
|
|
|
const char* Client::GetClassPlural(Client* client) {
|
|
|
|
switch (client->CastToMob()->GetClass()) {
|
|
case WARRIOR:
|
|
return "Warriors"; break;
|
|
case CLERIC:
|
|
return "Clerics"; break;
|
|
case PALADIN:
|
|
return "Paladins"; break;
|
|
case RANGER:
|
|
return "Rangers"; break;
|
|
case SHADOWKNIGHT:
|
|
return "Shadowknights"; break;
|
|
case DRUID:
|
|
return "Druids"; break;
|
|
case MONK:
|
|
return "Monks"; break;
|
|
case BARD:
|
|
return "Bards"; break;
|
|
case ROGUE:
|
|
return "Rogues"; break;
|
|
case SHAMAN:
|
|
return "Shamen"; break;
|
|
case NECROMANCER:
|
|
return "Necromancers"; break;
|
|
case WIZARD:
|
|
return "Wizards"; break;
|
|
case MAGICIAN:
|
|
return "Magicians"; break;
|
|
case ENCHANTER:
|
|
return "Enchanters"; break;
|
|
case BEASTLORD:
|
|
return "Beastlords"; break;
|
|
case BERSERKER:
|
|
return "Berserkers"; break;
|
|
default:
|
|
return "Classes"; break;
|
|
}
|
|
}
|
|
|
|
|
|
void Client::SendWebLink(const char *website)
|
|
{
|
|
if(website != 0)
|
|
{
|
|
string str = website;
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Weblink, sizeof(Weblink_Struct) + str.length() + 1);
|
|
Weblink_Struct *wl = (Weblink_Struct*)outapp->pBuffer;
|
|
memcpy(wl->weblink, str.c_str(), str.length() + 1);
|
|
wl->weblink[str.length() + 1] = '\0';
|
|
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
void Client::SendMercPersonalInfo()
|
|
{
|
|
uint32 mercTypeCount = 1;
|
|
uint32 mercCount = 1; //TODO: Un-hardcode this and support multiple mercs like in later clients than SoD.
|
|
//uint32 packetSize = 0;
|
|
uint32 i=0;
|
|
uint32 altCurrentType = 19; //TODO: Implement alternate currency purchases involving mercs!
|
|
|
|
if (GetClientVersion() >= EQClientRoF)
|
|
{
|
|
MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID];
|
|
|
|
if (mercData)
|
|
{
|
|
int i = 0;
|
|
int stancecount = 0;
|
|
stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size();
|
|
|
|
if(stancecount > MAX_MERC_STANCES || mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES)
|
|
{
|
|
SendMercMerchantResponsePacket(0);
|
|
return;
|
|
}
|
|
if (mercCount > 0 && mercCount)
|
|
{
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct));
|
|
MercenaryDataUpdate_Struct* mdus = (MercenaryDataUpdate_Struct*)outapp->pBuffer;
|
|
mdus->MercStatus = 0;
|
|
mdus->MercCount = mercCount;
|
|
mdus->MercData[i].MercID = mercData->MercTemplateID;
|
|
mdus->MercData[i].MercType = mercData->MercType;
|
|
mdus->MercData[i].MercSubType = mercData->MercSubType;
|
|
mdus->MercData[i].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0);
|
|
mdus->MercData[i].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0);
|
|
mdus->MercData[i].Status = 0;
|
|
mdus->MercData[i].AltCurrencyCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType);
|
|
mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType);
|
|
mdus->MercData[i].AltCurrencyType = altCurrentType;
|
|
mdus->MercData[i].MercUnk01 = 0;
|
|
mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime();
|
|
mdus->MercData[i].MerchantSlot = i + 1;
|
|
mdus->MercData[i].MercUnk02 = 1;
|
|
mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size();
|
|
mdus->MercData[i].MercUnk03 = 0;
|
|
mdus->MercData[i].MercUnk04 = 1;
|
|
strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name , sizeof(mdus->MercData[i].MercName));
|
|
uint32 stanceindex = 0;
|
|
if (mdus->MercData[i].StanceCount != 0)
|
|
{
|
|
list<MercStanceInfo>::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin();
|
|
while(iter != zone->merc_stance_list[mercData->MercTemplateID].end())
|
|
{
|
|
mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex;
|
|
mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID);
|
|
stanceindex++;
|
|
iter++;
|
|
}
|
|
}
|
|
|
|
mdus->MercData[i].MercUnk05 = 1;
|
|
FastQueuePacket(&outapp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int stancecount = 0;
|
|
stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size();
|
|
|
|
if(mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES)
|
|
{
|
|
if (GetClientVersion() == EQClientSoD)
|
|
{
|
|
SendMercMerchantResponsePacket(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct));
|
|
MercenaryMerchantList_Struct* mml = (MercenaryMerchantList_Struct*)outapp->pBuffer;
|
|
MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID];
|
|
|
|
|
|
if(mercData)
|
|
{
|
|
if(mercTypeCount > 0)
|
|
{
|
|
mml->MercTypeCount = mercTypeCount; //We only should have one merc entry.
|
|
mml->MercGrades[i] = 1;
|
|
}
|
|
mml->MercCount = mercCount;
|
|
if(mercCount > 0)
|
|
{
|
|
|
|
mml->Mercs[i].MercID = mercData->MercTemplateID;
|
|
mml->Mercs[i].MercType = mercData->MercType;
|
|
mml->Mercs[i].MercSubType = mercData->MercSubType;
|
|
mml->Mercs[i].PurchaseCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0): 0;
|
|
mml->Mercs[i].UpkeepCost = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0): 0;
|
|
mml->Mercs[i].Status = 0;
|
|
mml->Mercs[i].AltCurrencyCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0;
|
|
mml->Mercs[i].AltCurrencyUpkeep = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0;
|
|
mml->Mercs[i].AltCurrencyType = altCurrentType;
|
|
mml->Mercs[i].MercUnk01 = 0;
|
|
mml->Mercs[i].TimeLeft = GetMercInfo().MercTimerRemaining;
|
|
mml->Mercs[i].MerchantSlot = i + 1;
|
|
mml->Mercs[i].MercUnk02 = 1;
|
|
mml->Mercs[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size();
|
|
mml->Mercs[i].MercUnk03 = 0;
|
|
mml->Mercs[i].MercUnk04 = 1;
|
|
//mml->Mercs[i].MercName;
|
|
int stanceindex = 0;
|
|
if(mml->Mercs[i].StanceCount != 0)
|
|
{
|
|
list<MercStanceInfo>::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin();
|
|
while(iter != zone->merc_stance_list[mercData->MercTemplateID].end())
|
|
{
|
|
mml->Mercs[i].Stances[stanceindex].StanceIndex = stanceindex;
|
|
mml->Mercs[i].Stances[stanceindex].Stance = (iter->StanceID);
|
|
stanceindex++;
|
|
iter++;
|
|
}
|
|
}
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
else
|
|
{
|
|
safe_delete(outapp);
|
|
if (GetClientVersion() == EQClientSoD)
|
|
{
|
|
SendMercMerchantResponsePacket(0);
|
|
}
|
|
return;
|
|
}
|
|
if (GetClientVersion() == EQClientSoD)
|
|
{
|
|
SendMercMerchantResponsePacket(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
safe_delete(outapp);
|
|
if (GetClientVersion() == EQClientSoD)
|
|
{
|
|
SendMercMerchantResponsePacket(0);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::SendClearMercInfo()
|
|
{
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(NoMercenaryHired_Struct));
|
|
NoMercenaryHired_Struct *nmhs = (NoMercenaryHired_Struct*)outapp->pBuffer;
|
|
nmhs->MercStatus = -1;
|
|
nmhs->MercCount = 0;
|
|
nmhs->MercID = 1;
|
|
FastQueuePacket(&outapp);
|
|
}
|
|
|
|
|
|
void Client::DuplicateLoreMessage(uint32 ItemID)
|
|
{
|
|
if(!(ClientVersionBit & BIT_RoFAndLater))
|
|
{
|
|
Message_StringID(0, PICK_LORE);
|
|
return;
|
|
}
|
|
|
|
const Item_Struct *item = database.GetItem(ItemID);
|
|
|
|
if(!item)
|
|
return;
|
|
|
|
Message_StringID(0, PICK_LORE, item->Name);
|
|
}
|
|
|
|
void Client::GarbleMessage(char *message, uint8 variance)
|
|
{
|
|
// Garble message by variance%
|
|
const char alpha_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // only change alpha characters for now
|
|
|
|
for (size_t i = 0; i < strlen(message); i++) {
|
|
uint8 chance = (uint8)MakeRandomInt(0, 115); // variation just over worst possible scrambling
|
|
if (isalpha(message[i]) && (chance <= variance)) {
|
|
uint8 rand_char = (uint8)MakeRandomInt(0,51); // choose a random character from the alpha list
|
|
message[i] = alpha_list[rand_char];
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns what Other thinks of this
|
|
FACTION_VALUE Client::GetReverseFactionCon(Mob* iOther) {
|
|
if (GetOwnerID()) {
|
|
return GetOwnerOrSelf()->GetReverseFactionCon(iOther);
|
|
}
|
|
|
|
iOther = iOther->GetOwnerOrSelf();
|
|
|
|
if (iOther->GetPrimaryFaction() < 0)
|
|
return GetSpecialFactionCon(iOther);
|
|
|
|
if (iOther->GetPrimaryFaction() == 0)
|
|
return FACTION_INDIFFERENT;
|
|
|
|
return GetFactionLevel(CharacterID(), 0, GetRace(), GetClass(), GetDeity(), iOther->GetPrimaryFaction(), iOther);
|
|
}
|
|
|
|
//o--------------------------------------------------------------
|
|
//| Name: GetFactionLevel; rembrant, Dec. 16, 2001
|
|
//o--------------------------------------------------------------
|
|
//| Notes: Gets the characters faction standing with the
|
|
//| specified NPC.
|
|
//| Will return Indifferent on failure.
|
|
//o--------------------------------------------------------------
|
|
FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction, Mob* tnpc)
|
|
{
|
|
_ZP(Client_GetFactionLevel);
|
|
|
|
if (pFaction < 0)
|
|
return GetSpecialFactionCon(tnpc);
|
|
FACTION_VALUE fac = FACTION_INDIFFERENT;
|
|
//int32 pFacValue; -Trumpcard: commenting. Not currently used.
|
|
int32 tmpFactionValue;
|
|
FactionMods fmods;
|
|
|
|
// neotokyo: few optimizations
|
|
if (GetFeigned())
|
|
return FACTION_INDIFFERENT;
|
|
if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead())
|
|
return FACTION_INDIFFERENT;
|
|
if (IsInvisible(tnpc))
|
|
return FACTION_INDIFFERENT;
|
|
if (tnpc && tnpc->GetOwnerID() != 0) // pets con amiably to owner and indiff to rest
|
|
if (char_id == tnpc->GetOwner()->CastToClient()->CharacterID())
|
|
return FACTION_AMIABLE;
|
|
else
|
|
return FACTION_INDIFFERENT;
|
|
|
|
//First get the NPC's Primary faction
|
|
if(pFaction > 0)
|
|
{
|
|
//Get the faction data from the database
|
|
if(database.GetFactionData(&fmods, p_class, p_race, p_deity, pFaction))
|
|
{
|
|
//Get the players current faction with pFaction
|
|
tmpFactionValue = GetCharacterFactionLevel(pFaction);
|
|
// Everhood - tack on any bonuses from Alliance type spell effects
|
|
tmpFactionValue += GetFactionBonus(pFaction);
|
|
tmpFactionValue += GetItemFactionBonus(pFaction);
|
|
//Return the faction to the client
|
|
fac = CalculateFaction(&fmods, tmpFactionValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return(FACTION_INDIFFERENT);
|
|
}
|
|
|
|
// merchant fix
|
|
if (tnpc && tnpc->IsNPC() && tnpc->CastToNPC()->MerchantType && (fac == FACTION_THREATENLY || fac == FACTION_SCOWLS))
|
|
fac = FACTION_DUBIOUS;
|
|
|
|
if (tnpc != 0 && fac != FACTION_SCOWLS && tnpc->CastToNPC()->CheckAggro(this))
|
|
fac = FACTION_THREATENLY;
|
|
|
|
return fac;
|
|
}
|
|
|
|
//o--------------------------------------------------------------
|
|
//| Name: SetFactionLevel; rembrant, Dec. 20, 2001
|
|
//o--------------------------------------------------------------
|
|
//| Notes: Sets the characters faction standing with the
|
|
//| specified NPC.
|
|
//o--------------------------------------------------------------
|
|
void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity)
|
|
{
|
|
_ZP(Client_SetFactionLevel);
|
|
int32 faction_id[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 };
|
|
int32 npc_value[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 };
|
|
uint8 temp[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 };
|
|
int32 mod;
|
|
int32 t;
|
|
int32 tmpValue;
|
|
int32 current_value;
|
|
FactionMods fm;
|
|
// Get the npc faction list
|
|
if(!database.GetNPCFactionList(npc_id, faction_id, npc_value, temp))
|
|
return;
|
|
for(int i = 0;i<MAX_NPC_FACTIONS;i++)
|
|
{
|
|
if(faction_id[i] <= 0)
|
|
continue;
|
|
|
|
// Get the faction modifiers
|
|
if(database.GetFactionData(&fm,char_class,char_race,char_deity,faction_id[i]))
|
|
{
|
|
// Get the characters current value with that faction
|
|
current_value = GetCharacterFactionLevel(faction_id[i]);
|
|
|
|
if(this->itembonuses.HeroicCHA) {
|
|
int faction_mod = itembonuses.HeroicCHA / 5;
|
|
// If our result isn't truncated, then just do that
|
|
if(npc_value[i] * faction_mod / 100 != 0)
|
|
npc_value[i] += npc_value[i] * faction_mod / 100;
|
|
// If our result is truncated, then double a mob's value every once and a while to equal what they would have got
|
|
else {
|
|
if(MakeRandomInt(0, 100) < faction_mod)
|
|
npc_value[i] *= 2;
|
|
}
|
|
}
|
|
//figure out their modifier
|
|
mod = fm.base + fm.class_mod + fm.race_mod + fm.deity_mod;
|
|
if(mod > MAX_FACTION)
|
|
mod = MAX_FACTION;
|
|
else if(mod < MIN_FACTION)
|
|
mod = MIN_FACTION;
|
|
|
|
// Calculate the faction
|
|
if(npc_value[i] != 0) {
|
|
tmpValue = current_value + mod + npc_value[i];
|
|
|
|
// Make sure faction hits don't go to GMs...
|
|
if (m_pp.gm==1 && (tmpValue < current_value)) {
|
|
tmpValue = current_value;
|
|
}
|
|
|
|
// Make sure we dont go over the min/max faction limits
|
|
if(tmpValue >= MAX_FACTION)
|
|
{
|
|
t = MAX_FACTION - mod;
|
|
if(current_value == t) {
|
|
//do nothing, it is already maxed out
|
|
} else if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], t, temp[i], factionvalues)))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if(tmpValue <= MIN_FACTION)
|
|
{
|
|
t = MIN_FACTION - mod;
|
|
if(current_value == t) {
|
|
//do nothing, it is already maxed out
|
|
} else if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], t, temp[i], factionvalues)))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], current_value + npc_value[i], temp[i], factionvalues)))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if(tmpValue <= MIN_FACTION)
|
|
tmpValue = MIN_FACTION;
|
|
|
|
char* msg = BuildFactionMessage(npc_value[i],faction_id[i],tmpValue,temp[i]);
|
|
if (msg != 0)
|
|
Message(0, msg);
|
|
safe_delete_array(msg);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void Client::SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp)
|
|
{
|
|
_ZP(Client_SetFactionLevel2);
|
|
int32 current_value;
|
|
//Get the npc faction list
|
|
if(faction_id > 0 && value != 0) {
|
|
//Get the faction modifiers
|
|
current_value = GetCharacterFactionLevel(faction_id) + value;
|
|
if(!(database.SetCharacterFactionLevel(char_id, faction_id, current_value, temp, factionvalues)))
|
|
return;
|
|
|
|
char* msg = BuildFactionMessage(value, faction_id, current_value, temp);
|
|
if (msg != 0)
|
|
Message(0, msg);
|
|
safe_delete(msg);
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
int32 Client::GetCharacterFactionLevel(int32 faction_id)
|
|
{
|
|
if (faction_id <= 0)
|
|
return 0;
|
|
faction_map::iterator res;
|
|
res = factionvalues.find(faction_id);
|
|
if(res == factionvalues.end())
|
|
return(0);
|
|
return(res->second);
|
|
}
|
|
|
|
// returns the character's faction level, adjusted for racial, class, and deity modifiers
|
|
int32 Client::GetModCharacterFactionLevel(int32 faction_id) {
|
|
int32 Modded = GetCharacterFactionLevel(faction_id);
|
|
FactionMods fm;
|
|
if(database.GetFactionData(&fm,GetClass(),GetRace(),GetDeity(),faction_id))
|
|
Modded += fm.base + fm.class_mod + fm.race_mod + fm.deity_mod;
|
|
if (Modded > MAX_FACTION)
|
|
Modded = MAX_FACTION;
|
|
|
|
return Modded;
|
|
}
|
|
|
|
bool Client::HatedByClass(uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction)
|
|
{
|
|
|
|
bool Result = false;
|
|
_ZP(Client_GetFactionLevel);
|
|
|
|
int32 tmpFactionValue;
|
|
FactionMods fmods;
|
|
|
|
//First get the NPC's Primary faction
|
|
if(pFaction > 0)
|
|
{
|
|
//Get the faction data from the database
|
|
if(database.GetFactionData(&fmods, p_class, p_race, p_deity, pFaction))
|
|
{
|
|
tmpFactionValue = GetCharacterFactionLevel(pFaction);
|
|
tmpFactionValue += GetFactionBonus(pFaction);
|
|
tmpFactionValue += GetItemFactionBonus(pFaction);
|
|
CalculateFaction(&fmods, tmpFactionValue);
|
|
if(fmods.class_mod < fmods.race_mod)
|
|
Result = true;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
//o--------------------------------------------------------------
|
|
//| Name: BuildFactionMessage; rembrant, Dec. 16, 2001
|
|
//o--------------------------------------------------------------
|
|
//| Purpose: duh?
|
|
//o--------------------------------------------------------------
|
|
char* Client::BuildFactionMessage(int32 tmpvalue, int32 faction_id, int32 totalvalue, uint8 temp)
|
|
{
|
|
/*
|
|
|
|
This should be replaced to send string-ID based messages using:
|
|
#define FACTION_WORST 469 //Your faction standing with %1 could not possibly get any worse.
|
|
#define FACTION_WORSE 470 //Your faction standing with %1 got worse.
|
|
#define FACTION_BEST 471 //Your faction standing with %1 could not possibly get any better.
|
|
#define FACTION_BETTER 472 //Your faction standing with %1 got better.
|
|
|
|
some day.
|
|
|
|
*/
|
|
//tmpvalue is the change as best I can tell.
|
|
char *faction_message = 0;
|
|
|
|
char name[50];
|
|
|
|
if(database.GetFactionName(faction_id, name, sizeof(name)) == false) {
|
|
snprintf(name, sizeof(name),"Faction%i",faction_id);
|
|
}
|
|
|
|
if(tmpvalue == 0 || temp == 1 || temp == 2) {
|
|
return 0;
|
|
}
|
|
else if (totalvalue >= MAX_FACTION) {
|
|
MakeAnyLenString(&faction_message, "Your faction standing with %s could not possibly get any better!", name);
|
|
return faction_message;
|
|
}
|
|
else if(tmpvalue > 0 && totalvalue < MAX_FACTION) {
|
|
MakeAnyLenString(&faction_message, "Your faction standing with %s has gotten better!", name);
|
|
return faction_message;
|
|
}
|
|
else if(tmpvalue < 0 && totalvalue > MIN_FACTION) {
|
|
MakeAnyLenString(&faction_message, "Your faction standing with %s has gotten worse!", name);
|
|
return faction_message;
|
|
}
|
|
else if(totalvalue <= MIN_FACTION) {
|
|
MakeAnyLenString(&faction_message, "Your faction standing with %s could not possibly get any worse!", name);
|
|
return faction_message;
|
|
}
|
|
return 0;
|
|
}
|