zaela-Server/zone/client.cpp

8321 lines
224 KiB
C++
Raw Permalink Normal View History

2013-05-09 10:44:08 -04:00
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.org)
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
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.
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
2013-02-16 16:14:39 -08:00
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
2013-05-09 10:44:08 -04:00
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
2013-02-16 16:14:39 -08:00
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
2013-05-09 10:44:08 -04:00
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2013-02-16 16:14:39 -08:00
*/
#include "../common/debug.h"
#include <iostream>
#include <iomanip>
#include <sstream>
2013-02-16 16:14:39 -08:00
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <math.h>
// for windows compile
#ifdef _WINDOWS
#define abs64 _abs64
2013-02-16 16:14:39 -08:00
#else
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "../common/unix.h"
#define abs64 abs
2013-02-16 16:14:39 -08:00
#endif
extern volatile bool RunLoops;
2013-02-20 13:54:26 -08:00
#include "../common/features.h"
2013-02-16 16:14:39 -08:00
#include "../common/misc.h"
#include "../common/spdat.h"
2013-02-16 16:14:39 -08:00
#include "../common/packet_dump.h"
#include "../common/packet_functions.h"
#include "../common/serverinfo.h"
2014-08-21 19:33:02 -07:00
#include "../common/zone_numbers.h"
2013-02-16 16:14:39 -08:00
#include "../common/moremath.h"
#include "../common/guilds.h"
#include "../common/breakdowns.h"
#include "../common/rulesys.h"
2014-08-21 19:33:02 -07:00
#include "../common/string_util.h"
#include "../common/data_verification.h"
#include "net.h"
#include "masterentity.h"
#include "worldserver.h"
#include "zonedb.h"
#include "petitions.h"
2013-02-16 16:14:39 -08:00
#include "forage.h"
#include "command.h"
2014-08-21 23:46:01 -07:00
#include "string_ids.h"
#include "npc_ai.h"
2013-02-16 16:14:39 -08:00
#include "client_logs.h"
#include "guild_mgr.h"
2014-08-21 23:46:01 -07:00
#include "quest_parser_collection.h"
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
#include "queryserv.h"
2013-02-16 16:14:39 -08:00
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
extern QueryServ* QServ;
2013-02-16 16:14:39 -08:00
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];
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),
proximity_timer(ClientProximity_interval),
TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000),
2013-06-06 14:29:45 -07:00
charm_update_timer(6000),
2013-02-16 16:14:39 -08:00
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)),
ItemTickTimer(10000),
ItemQuestTimer(500)
2013-02-16 16:14:39 -08:00
{
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;
2013-05-04 18:06:58 -07:00
shield_target = nullptr;
SQL_log = nullptr;
2013-02-16 16:14:39 -08:00
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();
2013-02-16 16:14:39 -08:00
instalog = false;
pLastUpdate = 0;
pLastUpdateWZ = 0;
m_pp.autosplit = false;
// initialise haste variable
2013-05-04 18:06:58 -07:00
m_tradeskill_object = nullptr;
2013-02-16 16:14:39 -08:00
delaytimer = false;
PendingRezzXP = -1;
PendingRezzDBID = 0;
PendingRezzSpellID = 0;
numclients++;
// emuerror;
UpdateWindowTitle();
horseId = 0;
tgb = false;
tribute_master_id = 0xFFFFFFFF;
tribute_timer.Disable();
2013-05-04 18:06:58 -07:00
taskstate = nullptr;
2013-02-16 16:14:39 -08:00
TotalSecondsPlayed = 0;
keyring.clear();
2013-05-04 18:06:58 -07:00
bind_sight_target = nullptr;
2013-02-16 16:14:39 -08:00
mercid = 0;
mercSlot = 0;
InitializeMercInfo();
2013-02-16 16:14:39 -08:00
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_AssistExemption = 0;
2013-02-16 16:14:39 -08:00
m_CheatDetectMoved = false;
CanUseReport = true;
aa_los_me.x = 0;
aa_los_me.y = 0;
aa_los_me.z = 0;
aa_los_me_heading = 0;
2013-02-16 16:14:39 -08:00
aa_los_them.x = 0;
aa_los_them.y = 0;
aa_los_them.z = 0;
2013-05-04 18:06:58 -07:00
aa_los_them_mob = nullptr;
2013-02-16 16:14:39 -08:00
los_status = false;
los_status_facing = false;
2013-05-04 18:06:58 -07:00
qGlobals = nullptr;
2013-02-16 16:14:39 -08:00
HideCorpseMode = HideCorpseNone;
PendingGuildInvitation = false;
cur_end = 0;
InitializeBuffSlots();
2013-05-04 18:06:58 -07:00
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;
2013-02-16 16:14:39 -08:00
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;
}
2013-05-06 13:07:41 -04:00
MaxXTargets = 5;
2013-02-16 16:14:39 -08:00
XTargetAutoAddHaters = true;
LoadAccountFlags();
initial_respawn_selection = 0;
alternate_currency_loaded = false;
2014-08-12 15:18:11 -07:00
EngagedRaidTarget = false;
SavedRaidRestTimer = 0;
2013-02-16 16:14:39 -08:00
}
Client::~Client() {
#ifdef BOTS
Bot::ProcessBotOwnerRefDelete(this);
#endif
if(IsInAGuild())
2013-05-04 18:06:58 -07:00
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
2013-02-16 16:14:39 -08:00
Mob* horse = entity_list.GetMob(this->CastToClient()->GetHorseId());
if (horse)
horse->Depop();
Mob* merc = entity_list.GetMob(this->GetMercID());
if (merc)
merc->Depop();
2013-02-16 16:14:39 -08:00
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();
}
2013-05-04 18:06:58 -07:00
if(m_tradeskill_object != nullptr) {
2013-02-16 16:14:39 -08:00
m_tradeskill_object->Close();
2013-05-04 18:06:58 -07:00
m_tradeskill_object = nullptr;
2013-02-16 16:14:39 -08:00
}
#ifdef CLIENT_LOGS
client_logs.unsubscribeAll(this);
#endif
2013-05-04 18:06:58 -07:00
ChangeSQLLog(nullptr);
2013-02-16 16:14:39 -08:00
if(IsDueling() && GetDuelTarget() != 0) {
Entity* entity = entity_list.GetID(GetDuelTarget());
2013-05-04 18:06:58 -07:00
if(entity != nullptr && entity->IsClient()) {
2013-02-16 16:14:39 -08:00
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;
}
}
2013-05-04 18:06:58 -07:00
shield_target = nullptr;
2013-02-16 16:14:39 -08:00
}
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);
ClearRespawnOptions();
2013-02-16 16:14:39 -08:00
numclients--;
UpdateWindowTitle();
if(zone)
zone->RemoveAuth(GetName());
//let the stream factory know were done with this stream
eqs->Close();
eqs->ReleaseFromUse();
2013-02-16 16:14:39 -08:00
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
2013-05-09 10:44:08 -04:00
LogFile->write(EQEMuLog::Debug, "Client is successfully connected.");
2013-02-16 16:14:39 -08:00
break;
};
}
bool Client::SaveAA(){
int first_entry = 0;
std::string rquery;
/* Save Player AA */
int spentpoints = 0;
for (int a = 0; a < MAX_PP_AA_ARRAY; a++) {
2013-02-16 16:14:39 -08:00
uint32 points = aa[a]->value;
if (points > HIGHEST_AA_VALUE) {
2013-02-16 16:14:39 -08:00
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()) {
2013-05-09 10:44:08 -04:00
spentpoints += RequiredLevel->second.Cost;
2013-02-16 16:14:39 -08:00
}
else
spentpoints += (curAA->cost + (curAA->cost_inc * rank));
}
}
}
}
m_pp.aapoints_spent = spentpoints + m_epp.expended_aa;
for (int a = 0; a < MAX_PP_AA_ARRAY; a++) {
if (aa[a]->AA > 0 && aa[a]->value){
if (first_entry != 1){
rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, slot, aa_id, aa_value)"
" VALUES (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value);
first_entry = 1;
}
rquery = rquery + StringFormat(", (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value);
}
}
auto results = database.QueryDatabase(rquery);
return true;
}
2013-02-16 16:14:39 -08:00
bool Client::Save(uint8 iCommitNow) {
if(!ClientDataLoaded())
return false;
2013-02-16 16:14:39 -08:00
/* Wrote current basics to PP for saves */
m_pp.x = x_pos;
m_pp.y = y_pos;
m_pp.z = z_pos;
m_pp.guildrank = guildrank;
m_pp.heading = heading;
/* Mana and HP */
if (GetHP() <= 0) {
m_pp.cur_hp = GetMaxHP();
}
else {
m_pp.cur_hp = GetHP();
}
2013-02-16 16:14:39 -08:00
m_pp.mana = cur_mana;
m_pp.endurance = cur_end;
/* Save Character Currency */
database.SaveCharacterCurrency(this->CharacterID(), &m_pp);
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
/* Save Current Bind Points : Sets Instance to 0 because it is currently not implemented */
database.SaveCharacterBindPoint(this->CharacterID(), m_pp.binds[0].zoneId, 0, m_pp.binds[0].x, m_pp.binds[0].y, m_pp.binds[0].z, 0, 0); /* Regular bind */
database.SaveCharacterBindPoint(this->CharacterID(), m_pp.binds[4].zoneId, 0, m_pp.binds[4].x, m_pp.binds[4].y, m_pp.binds[4].z, 0, 1); /* Home Bind */
/* Save Character Buffs */
2013-02-16 16:14:39 -08:00
database.SaveBuffs(this);
/* Total Time Played */
2013-05-04 18:06:58 -07:00
TotalSecondsPlayed += (time(nullptr) - m_pp.lastlogin);
2013-02-16 16:14:39 -08:00
m_pp.timePlayedMin = (TotalSecondsPlayed / 60);
m_pp.RestTimer = rest_timer.GetRemainingTime() / 1000;
/* Save Mercs */
if (GetMercInfo().MercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS)) {
GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS);
}
if (GetMercTimer()->Enabled()) {
GetMercInfo().MercTimerRemaining = GetMercTimer()->GetRemainingTime();
}
if (!(GetMerc() && !dead)) {
memset(&m_mercinfo, 0, sizeof(struct MercInfo));
}
2013-02-16 16:14:39 -08:00
2013-05-04 18:06:58 -07:00
m_pp.lastlogin = time(nullptr);
2013-02-16 16:14:39 -08:00
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();
m_petinfo.size = pet->GetSize();
2013-02-16 16:14:39 -08:00
} 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;
}
2013-02-16 16:14:39 -08:00
p_timers.Store(&database);
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
database.SaveCharacterTribute(this->CharacterID(), &m_pp);
SaveTaskState(); /* Save Character Task */
m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level, 0, 50000);
m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level, 0, 50000);
database.SaveCharacterData(this->CharacterID(), this->AccountID(), &m_pp, &m_epp); /* Save Character Data */
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
2013-02-16 16:14:39 -08:00
return true;
}
void Client::SaveBackup() {
}
CLIENTPACKET::CLIENTPACKET()
{
2013-05-09 10:44:08 -04:00
app = nullptr;
ack_req = false;
2013-02-16 16:14:39 -08:00
}
CLIENTPACKET::~CLIENTPACKET()
{
2013-05-09 10:44:08 -04:00
safe_delete(app);
2013-02-16 16:14:39 -08:00
}
//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);
}
2013-05-09 10:44:08 -04:00
CLIENTPACKET *c = new CLIENTPACKET;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
c->ack_req = bAckreq;
c->app = pApp->Copy();
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
clientpackets.Append(c);
return true;
2013-02-16 16:14:39 -08:00
}
//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);
}
2013-05-09 10:44:08 -04:00
CLIENTPACKET *c = new CLIENTPACKET;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
c->ack_req = bAckreq;
c->app = *pApp;
2013-02-16 16:14:39 -08:00
*pApp = 0;
2013-05-09 10:44:08 -04:00
clientpackets.Append(c);
return true;
2013-02-16 16:14:39 -08:00
}
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(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
2013-05-09 10:44:08 -04:00
if (required_state != CLIENT_CONNECTINGALL && client_state != required_state)
{
// todo: save packets for later use
AddPacket(app, ack_req);
}
else
if(eqs)
eqs->QueuePacket(app, ack_req);
2013-02-16 16:14:39 -08:00
}
void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) {
// if the program doesnt care about the status or if the status isnt what we requested
2013-05-09 10:44:08 -04:00
if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) {
// todo: save packets for later use
AddPacket(app, ack_req);
2013-02-16 16:14:39 -08:00
return;
2013-05-09 10:44:08 -04:00
}
else {
if(eqs)
eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req);
2013-02-16 16:14:39 -08:00
else if (app && (*app))
delete *app;
*app = 0;
}
return;
}
2013-02-17 23:38:51 -05:00
void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_skill, const char* orig_message, const char* targetname) {
char message[4096];
2013-02-18 02:05:59 -05:00
strn0cpy(message, orig_message, sizeof(message));
2013-02-17 23:38:51 -05:00
2013-02-16 16:14:39 -08:00
#if EQDEBUG >= 11
LogFile->write(EQEMuLog::Debug,"Client::ChannelMessageReceived() Channel:%i message:'%s'", chan_num, message);
#endif
2013-05-04 18:06:58 -07:00
if (targetname == nullptr) {
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages;
2013-02-16 16:14:39 -08:00
if(RuleI(Chat, MinStatusToBypassAntiSpam) <= Admin())
AllowedMessages = 10000;
AttemptedMessages++;
if(AttemptedMessages > AllowedMessages)
{
if(AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick))
{
Kick();
return;
}
if(GlobalChatLimiterTimer)
{
2013-05-06 13:07:41 -04:00
Message(0, "You have been rate limited, you can send more messages in %i seconds.",
2013-02-16 16:14:39 -08:00
GlobalChatLimiterTimer->GetRemainingTime() / 1000);
return;
}
else
{
Message(0, "You have been rate limited, you can send more messages in 60 seconds.");
return;
}
}
}
}
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
/* Logs Player Chat */
if (RuleB(QueryServ, PlayerLogChat)) {
2013-02-17 23:38:51 -05:00
ServerPacket* pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1);
2013-02-16 16:14:39 -08:00
Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(chan_num == 0)
sem->guilddbid = GuildID();
else
sem->guilddbid = 0;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
strcpy(sem->message, message);
sem->minstatus = this->Admin();
sem->type = chan_num;
if(targetname != 0)
2013-02-17 23:38:51 -05:00
strcpy(sem->to, targetname);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(GetName() != 0)
2013-02-17 23:38:51 -05:00
strcpy(sem->from, GetName());
2013-02-16 16:14:39 -08:00
pack->Deflate();
if(worldserver.Connected())
worldserver.SendPacket(pack);
safe_delete(pack);
}
//Return true to proceed, false to return
if(!mod_client_message(message, chan_num)) { return; }
2013-02-17 23:38:51 -05:00
// 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
}
2013-02-16 16:14:39 -08:00
switch(chan_num)
{
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 0: { /* Guild Chat */
2013-02-16 16:14:39 -08:00
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;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 2: { /* Group Chat */
2013-02-16 16:14:39 -08:00
Raid* raid = entity_list.GetRaidByClient(this);
2013-02-17 23:38:51 -05:00
if(raid) {
2013-02-16 16:14:39 -08:00
raid->RaidGroupSay((const char*) message, this);
break;
}
Group* group = GetGroup();
2013-05-04 18:06:58 -07:00
if(group != nullptr) {
2013-02-16 16:14:39 -08:00
group->GroupMessage(this,language,lang_skill,(const char*) message);
}
break;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 15: { /* Raid Say */
2013-02-16 16:14:39 -08:00
Raid* raid = entity_list.GetRaidByClient(this);
if(raid){
raid->RaidSay((const char*) message, this);
}
break;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 3: { /* Shout */
2013-02-16 16:14:39 -08:00
Mob *sender = this;
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
sender = GetPet();
entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message);
2013-05-09 10:44:08 -04:00
break;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 4: { /* Auction */
2013-05-09 10:44:08 -04:00
if(RuleB(Chat, ServerWideAuction))
2013-02-16 16:14:39 -08:00
{
if(!global_channel_timer.Check())
{
2013-02-17 23:38:51 -05:00
if(strlen(targetname) == 0)
2013-02-16 16:14:39 -08:00
ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer??
else
return;
}
if(GetRevoked())
{
2013-05-09 10:44:08 -04:00
Message(0, "You have been revoked. You may not talk on Auction.");
2013-02-16 16:14:39 -08:00
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;
}
}
2013-05-09 10:44:08 -04:00
if (!worldserver.SendChannelMessage(this, 0, 4, 0, language, message))
2013-02-16 16:14:39 -08:00
Message(0, "Error: World server disconnected");
}
2013-02-17 23:38:51 -05:00
else if(!RuleB(Chat, ServerWideAuction)) {
2013-02-16 16:14:39 -08:00
Mob *sender = this;
2013-05-09 10:44:08 -04:00
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
2013-02-16 16:14:39 -08:00
sender = GetPet();
entity_list.ChannelMessage(sender, chan_num, language, message);
}
break;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 5: { /* OOC */
2013-02-16 16:14:39 -08:00
if(RuleB(Chat, ServerWideOOC))
{
if(!global_channel_timer.Check())
{
2013-02-17 23:38:51 -05:00
if(strlen(targetname) == 0)
ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer??
2013-02-16 16:14:39 -08:00
else
return;
}
if(worldserver.IsOOCMuted() && admin < 100)
{
2013-05-09 10:44:08 -04:00
Message(0,"OOC has been muted. Try again later.");
2013-02-16 16:14:39 -08:00
return;
}
if(GetRevoked())
{
2013-05-09 10:44:08 -04:00
Message(0, "You have been revoked. You may not talk on OOC.");
2013-02-16 16:14:39 -08:00
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;
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 6: /* Broadcast */
case 11: { /* GM Say */
2013-02-16 16:14:39 -08:00
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;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 7: { /* Tell */
2013-02-16 16:14:39 -08:00
if(!global_channel_timer.Check())
{
2013-02-17 23:38:51 -05:00
if(strlen(targetname) == 0)
2013-02-16 16:14:39 -08:00
ChannelMessageReceived(7, language, lang_skill, message, "discard"); //Fast typer or spammer??
else
return;
}
if(GetRevoked())
{
2013-05-09 10:44:08 -04:00
Message(0, "You have been revoked. You may not send tells.");
2013-02-16 16:14:39 -08:00
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';
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
if(!worldserver.SendChannelMessage(this, target_name, chan_num, 0, language, message))
Message(0, "Error: World server disconnected");
break;
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
case 8: { /* Say */
2013-05-09 10:44:08 -04:00
if(message[0] == COMMAND_CHAR) {
2013-02-17 23:38:51 -05:00
if(command_dispatch(this, message) == -2) {
if(parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(13, "Command '%s' not recognized.", message);
}
} else {
if(!RuleB(Chat, SuppressCommandErrors))
Message(13, "Command '%s' not recognized.", message);
}
2013-02-16 16:14:39 -08:00
}
break;
}
2013-02-16 16:14:39 -08:00
Mob* sender = this;
if (GetPet() && GetPet()->FindType(SE_VoiceGraft))
sender = GetPet();
entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message);
parse->EventPlayer(EVENT_SAY, this, message, language);
2013-02-16 16:14:39 -08:00
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());
2013-02-17 23:38:51 -05:00
CheckEmoteHail(GetTarget(), message);
2013-02-16 16:14:39 -08:00
if(DistNoRootNoZ(*GetTarget()) <= 200) {
NPC *tar = GetTarget()->CastToNPC();
parse->EventNPC(EVENT_SAY, tar->CastToNPC(), this, message, language);
2013-02-16 16:14:39 -08:00
if(RuleB(TaskSystem, EnableTaskSystem)) {
if(UpdateTasksOnSpeakWith(tar->GetNPCTypeID())) {
tar->DoQuestPause(this);
}
2013-02-16 16:14:39 -08:00
}
}
}
else {
if (DistNoRootNoZ(*GetTarget()) <= 200) {
parse->EventNPC(EVENT_AGGRO_SAY, GetTarget()->CastToNPC(), this, message, language);
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
}
}
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.
// crash protection -- cheater
message[1023] = '\0';
size_t msg_len = strlen(message);
if (msg_len > 512)
message[512] = '\0';
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Emote, 4 + msg_len + strlen(GetName()) + 2);
2013-02-16 16:14:39 -08:00
Emote_Struct* es = (Emote_Struct*)outapp->pBuffer;
char *Buffer = (char *)es;
Buffer += 4;
snprintf(Buffer, sizeof(Emote_Struct) - 4, "%s %s", GetName(), message);
2013-02-17 23:38:51 -05:00
entity_list.QueueCloseClients(this, outapp, true, 100, 0, true, FilterSocials);
2013-02-16 16:14:39 -08:00
safe_delete(outapp);
break;
}
default: {
2013-02-17 23:38:51 -05:00
Message(0, "Channel (%i) not implemented", (uint16)chan_num);
2013-02-16 16:14:39 -08:00
}
}
}
// 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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
uint8 ListenerSkill;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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));
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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
2013-05-06 13:07:41 -04:00
if ((m_pp.languages[language] <= lang_skill) && (from != this->GetName()))
2013-02-16 16:14:39 -08:00
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;
2013-05-09 10:44:08 -04:00
va_list argptr;
char *buffer = new char[4096];
2013-02-16 16:14:39 -08:00
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) {
2013-05-09 10:44:08 -04:00
// 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;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
char OutNPCName[MaxNPCNameLength+1];
char OutMessage[MaxMessageLength+1];
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
// 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';
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
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;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
sm->header[0] = 0;
sm->header[1] = 2;
sm->header[2] = 0;
sm->msg_type = 0x0a;
sm->target_spawn_id = GetID();
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
char *dest = &sm->sayer[0];
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
memcpy(dest, OutNPCName, strlen(OutNPCName) + 1);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
dest = dest + strlen(OutNPCName) + 13;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
memcpy(dest, OutMessage, strlen(OutMessage) + 1);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
QueuePacket(app);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
safe_delete(app);
2013-02-16 16:14:39 -08:00
}
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(SkillUseTypes skillid, uint16 value) {
2013-02-16 16:14:39 -08:00
if (skillid > HIGHEST_SKILL)
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
return;
m_pp.skills[skillid] = value; // We need to be able to #setskill 254 and 255 to reset skills
database.SaveCharacterSkill(this->CharacterID(), skillid, value);
2013-02-16 16:14:39 -08:00
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct));
SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
skill->skillId=skillid;
2013-02-16 16:14:39 -08:00
skill->value=value;
QueuePacket(outapp);
safe_delete(outapp);
}
2013-05-06 13:07:41 -04:00
void Client::IncreaseLanguageSkill(int skill_id, int value) {
2013-02-16 16:14:39 -08:00
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;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
database.SaveCharacterLanguage(this->CharacterID(), skill_id, m_pp.languages[skill_id]);
2013-02-16 16:14:39 -08:00
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct));
SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer;
skill->skillId = 100 + skill_id;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
skill->value = m_pp.languages[skill_id];
2013-02-16 16:14:39 -08:00
QueuePacket(outapp);
safe_delete(outapp);
Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify client
}
void Client::AddSkill(SkillUseTypes skillid, uint16 value) {
2013-02-16 16:14:39 -08:00
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
2013-05-09 10:44:08 -04:00
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));
2013-02-16 16:14:39 -08:00
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);
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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();
}
//std::cout << "Sending mana update: " << (cur_mana - last_reported_mana) << std::endl;
2013-02-16 16:14:39 -08:00
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();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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));
2013-05-06 13:07:41 -04:00
ManaUpdate_Struct* mus = (ManaUpdate_Struct*)mana_app->pBuffer;
2013-02-16 16:14:39 -08:00
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));
2013-05-06 13:07:41 -04:00
EnduranceUpdate_Struct* eus = (EnduranceUpdate_Struct*)end_app->pBuffer;
2013-02-16 16:14:39 -08:00
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!)
2013-05-04 18:06:58 -07:00
const Item_Struct* item = nullptr;
const ItemInst* inst = nullptr;
if ((inst = m_inv[MainHands]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialHands] = item->Material;
ns->spawn.colors[MaterialHands].color = GetEquipmentColor(MaterialHands);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainHead]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialHead] = item->Material;
ns->spawn.colors[MaterialHead].color = GetEquipmentColor(MaterialHead);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainArms]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialArms] = item->Material;
ns->spawn.colors[MaterialArms].color = GetEquipmentColor(MaterialArms);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainWrist1]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialWrist]= item->Material;
ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist);
2013-02-16 16:14:39 -08:00
}
/*
// non-live behavior
2013-02-16 16:14:39 -08:00
if ((inst = m_inv[SLOT_BRACER02]) && inst->IsType(ItemClassCommon)) {
item = inst->GetItem();
ns->spawn.equipment[MaterialWrist]= item->Material;
ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist);
2013-02-16 16:14:39 -08:00
}
*/
if ((inst = m_inv[MainChest]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialChest] = item->Material;
ns->spawn.colors[MaterialChest].color = GetEquipmentColor(MaterialChest);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainLegs]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialLegs] = item->Material;
ns->spawn.colors[MaterialLegs].color = GetEquipmentColor(MaterialLegs);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainFeet]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
ns->spawn.equipment[MaterialFeet] = item->Material;
ns->spawn.colors[MaterialFeet].color = GetEquipmentColor(MaterialFeet);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainPrimary]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
if (strlen(item->IDFile) > 2)
ns->spawn.equipment[MaterialPrimary] = atoi(&item->IDFile[2]);
2013-02-16 16:14:39 -08:00
}
if ((inst = m_inv[MainSecondary]) && inst->IsType(ItemClassCommon)) {
2013-02-16 16:14:39 -08:00
item = inst->GetItem();
if (strlen(item->IDFile) > 2)
ns->spawn.equipment[MaterialSecondary] = atoi(&item->IDFile[2]);
2013-02-16 16:14:39 -08:00
}
//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;
}
std::string booktxt2 = database.GetBook(txtfile);
2013-02-16 16:14:39 -08:00
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) {
std::string booktxt2 = text;
2013-02-16 16:14:39 -08:00
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();
SaveCurrency();
2013-02-16 16:14:39 -08:00
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();
SaveCurrency();
2013-02-16 16:14:39 -08:00
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();
SaveCurrency();
2013-02-16 16:14:39 -08:00
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();
SaveCurrency();
2013-02-16 16:14:39 -08:00
return true;
}
}
void Client::AddMoneyToPP(uint64 copper, bool updateclient){
uint64 tmp;
uint64 tmp2;
tmp = copper;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
/* Add Amount of Platinum */
2013-02-16 16:14:39 -08:00
tmp2 = tmp/1000;
2013-05-09 10:44:08 -04:00
int32 new_val = m_pp.platinum + tmp2;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
if(new_val < 0) { m_pp.platinum = 0; }
else { m_pp.platinum = m_pp.platinum + tmp2; }
2013-02-16 16:14:39 -08:00
tmp-=tmp2*1000;
2013-05-09 10:44:08 -04:00
//if (updateclient)
2013-02-16 16:14:39 -08:00
// SendClientMoneyUpdate(3,tmp2);
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
/* Add Amount of Gold */
2013-02-16 16:14:39 -08:00
tmp2 = tmp/100;
2013-05-09 10:44:08 -04:00
new_val = m_pp.gold + tmp2;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
if(new_val < 0) { m_pp.gold = 0; }
else { m_pp.gold = m_pp.gold + tmp2; }
2013-02-16 16:14:39 -08:00
tmp-=tmp2*100;
//if (updateclient)
2013-05-09 10:44:08 -04:00
// SendClientMoneyUpdate(2,tmp2);
2013-02-16 16:14:39 -08:00
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
/* Add Amount of Silver */
2013-02-16 16:14:39 -08:00
tmp2 = tmp/10;
2013-05-09 10:44:08 -04:00
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)
2013-02-16 16:14:39 -08:00
// SendClientMoneyUpdate(1,tmp2);
// Add Copper
//tmp = tmp - (tmp2* 10);
2013-05-09 10:44:08 -04:00
//if (updateclient)
2013-02-16 16:14:39 -08:00
// SendClientMoneyUpdate(0,tmp);
tmp2 = tmp;
2013-05-09 10:44:08 -04:00
new_val = m_pp.copper + tmp2;
if(new_val < 0) {
m_pp.copper = 0;
} else {
m_pp.copper = m_pp.copper + tmp2;
}
2013-02-16 16:14:39 -08:00
//send them all at once, since the above code stopped working.
if(updateclient)
SendMoneyUpdate();
RecalcWeight();
SaveCurrency();
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
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);
2013-02-16 16:14:39 -08:00
}
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
void Client::EVENT_ITEM_ScriptStopReturn(){
/* Set a timestamp in an entity variable for plugin check_handin.pl in return_items
This will stopgap players from items being returned if global_npc.pl has a catch all return_items
*/
struct timeval read_time;
char buffer[50];
gettimeofday(&read_time, 0);
sprintf(buffer, "%li.%li \n", read_time.tv_sec, read_time.tv_usec);
this->SetEntityVariable("Stop_Return", buffer);
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
}
void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool updateclient){
this->EVENT_ITEM_ScriptStopReturn();
2013-05-09 10:44:08 -04:00
int32 new_value = m_pp.platinum + platinum;
if(new_value >= 0 && new_value > m_pp.platinum)
m_pp.platinum += platinum;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
new_value = m_pp.gold + gold;
if(new_value >= 0 && new_value > m_pp.gold)
m_pp.gold += gold;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
new_value = m_pp.silver + silver;
if(new_value >= 0 && new_value > m_pp.silver)
m_pp.silver += silver;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
new_value = m_pp.copper + copper;
if(new_value >= 0 && new_value > m_pp.copper)
m_pp.copper += copper;
2013-02-16 16:14:39 -08:00
if(updateclient)
SendMoneyUpdate();
RecalcWeight();
SaveCurrency();
2013-02-16 16:14:39 -08:00
#if (EQDEBUG>=5)
2013-05-09 10:44:08 -04:00
LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i",
2013-02-16 16:14:39 -08:00
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) {
2013-05-09 10:44:08 -04:00
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)
2013-02-16 16:14:39 -08:00
return true;
return false;
}
uint64 Client::GetCarriedMoney() {
return ((static_cast<uint64>(m_pp.copper) +
2013-05-09 10:44:08 -04:00
(static_cast<uint64>(m_pp.silver) * 10) +
(static_cast<uint64>(m_pp.gold) * 100) +
(static_cast<uint64>(m_pp.platinum) * 1000)));
2013-02-16 16:14:39 -08:00
}
uint64 Client::GetAllMoney() {
return (
2013-05-09 10:44:08 -04:00
(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)))));
2013-02-16 16:14:39 -08:00
}
bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int chancemodi) {
if (IsDead() || IsUnconscious())
return false;
2013-02-16 16:14:39 -08:00
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->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() ||
2013-05-06 13:07:41 -04:00
GetLevelCon(against_who->GetLevel()) == CON_GREEN)
2013-02-16 16:14:39 -08:00
{
//false by default
if( !mod_can_increase_skill(skillid, against_who) ) { return(false); }
2013-02-16 16:14:39 -08:00
}
}
// 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);
2013-02-16 16:14:39 -08:00
Chance = (Chance * RuleI(Character, SkillUpModifier) / 100);
Chance = mod_increase_skill_chance(Chance, against_who);
if(Chance < 1)
Chance = 1; // Make it always possible
2013-02-16 16:14:39 -08:00
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 (IsDead() || IsUnconscious())
return;
if (IsAIControlled())
return;
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
}
2013-02-16 16:14:39 -08:00
else
_log(SKILLS__GAIN, "Language %d at value %d failed to gain with %.4f%%chance", langid, LangSkill, Chance);
}
}
bool Client::HasSkill(SkillUseTypes skill_id) const {
2013-02-16 16:14:39 -08:00
return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id));
}
bool Client::CanHaveSkill(SkillUseTypes skill_id) const {
2013-02-16 16:14:39 -08:00
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(SkillUseTypes skillid, uint16 class_, uint16 level) const {
2013-02-16 16:14:39 -08:00
return(database.GetSkillCap(class_, skillid, level));
}
uint8 Client::SkillTrainLevel(SkillUseTypes skillid, uint16 class_){
2013-02-16 16:14:39 -08:00
return(database.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel)));
}
uint16 Client::GetMaxSkillAfterSpecializationRules(SkillUseTypes skillid, uint16 maxSkill)
2013-02-16 16:14:39 -08:00
{
uint16 Result = maxSkill;
uint16 PrimarySpecialization = 0, SecondaryForte = 0;
uint16 PrimarySkillValue = 0, SecondarySkillValue = 0;
uint16 MaxSpecializations = GetAA(aaSecondaryForte) ? 2 : 1;
if(skillid >= SkillSpecializeAbjure && skillid <= SkillSpecializeEvocation)
2013-02-16 16:14:39 -08:00
{
bool HasPrimarySpecSkill = false;
int NumberOfPrimarySpecSkills = 0;
for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i)
2013-02-16 16:14:39 -08:00
{
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. "
2013-05-09 10:44:08 -04:00
"Only %i primary specialization skill is allowed.", MaxSpecializations);
2013-02-16 16:14:39 -08:00
for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i)
SetSkill((SkillUseTypes)i, 1);
2013-02-16 16:14:39 -08:00
Save();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
LogFile->write(EQEMuLog::Normal, "Reset %s's caster specialization skills to 1. "
2013-05-09 10:44:08 -04:00
"Too many specializations skills were above 50.", GetCleanName());
2013-02-16 16:14:39 -08:00
}
}
}
// This should possibly be handled by bonuses rather than here.
switch(skillid)
{
case SkillTracking:
2013-02-16 16:14:39 -08:00
{
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;
std::string LogText = "Qty: ";
2013-02-16 16:14:39 -08:00
char Buffer[255];
memset(Buffer, 0, sizeof(Buffer));
2013-05-06 13:07:41 -04:00
snprintf(Buffer, sizeof(Buffer)-1, "%3i", quantity);
2013-02-16 16:14:39 -08:00
LogText += Buffer;
2013-05-06 13:07:41 -04:00
snprintf(Buffer, sizeof(Buffer)-1, "%10i", price);
2013-02-16 16:14:39 -08:00
LogText += " TotalValue: ";
LogText += Buffer;
2013-05-06 13:07:41 -04:00
snprintf(Buffer, sizeof(Buffer)-1, " ItemID: %7i", item->ID);
2013-02-16 16:14:39 -08:00
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 == INVALID_INDEX) {
2013-02-16 16:14:39 -08:00
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
bind_out->type = 1; // Done
QueuePacket(outapp);
bind_out->type = 0;
CheckIncreaseSkill(SkillBindWound, nullptr, 5);
2013-02-16 16:14:39 -08:00
int maxHPBonus = spellbonuses.MaxBindWound + itembonuses.MaxBindWound + aabonuses.MaxBindWound;
int max_percent = 50 + 10 * maxHPBonus;
if(GetClass() == MONK && GetSkill(SkillBindWound) > 200) {
2013-02-16 16:14:39 -08:00
max_percent = 70 + 10 * maxHPBonus;
}
max_percent = mod_bindwound_percent(max_percent, bindmob);
2013-02-16 16:14:39 -08:00
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(SkillBindWound) > 200) {
bindhps += GetSkill(SkillBindWound)*4/10;
} else if (GetSkill(SkillBindWound) >= 10) {
bindhps += GetSkill(SkillBindWound)/4;
2013-02-16 16:14:39 -08:00
}
//Implementation of aaMithanielsBinding is a guess (the multiplier)
int bindBonus = spellbonuses.BindWound + itembonuses.BindWound + aabonuses.BindWound;
bindhps += bindhps*bindBonus / 100;
bindhps = mod_bindwound_hp(bindhps, bindmob);
2013-02-16 16:14:39 -08:00
//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) {
2013-02-16 16:14:39 -08:00
const Item_Struct* item = database.GetItem(item_id);
if (item && (item->ItemClass==ItemClassCommon)) {
if (in_slot==MainHead)
m_pp.item_material[MaterialHead] = item->Material;
else if (in_slot==MainChest)
m_pp.item_material[MaterialChest] = item->Material;
else if (in_slot==MainArms)
m_pp.item_material[MaterialArms] = item->Material;
else if (in_slot==MainWrist1)
m_pp.item_material[MaterialWrist] = item->Material;
else if (in_slot==MainHands)
m_pp.item_material[MaterialHands] = item->Material;
else if (in_slot==MainLegs)
m_pp.item_material[MaterialLegs] = item->Material;
else if (in_slot==MainFeet)
m_pp.item_material[MaterialFeet] = item->Material;
else if (in_slot==MainPrimary)
m_pp.item_material[MaterialPrimary] = atoi(item->IDFile+2);
else if (in_slot==MainSecondary)
m_pp.item_material[MaterialSecondary] = atoi(item->IDFile+2);
2013-02-16 16:14:39 -08:00
}
}
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;
if (filter->filters[FilterMeleeCrits] == 0)
ClientFilters[FilterMeleeCrits] = FilterShow;
else if (filter->filters[FilterMeleeCrits] == 1)
ClientFilters[FilterMeleeCrits] = FilterShowSelfOnly;
else
ClientFilters[FilterMeleeCrits] = FilterHide;
2013-02-16 16:14:39 -08:00
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);
if (GetClientVersionBit() & BIT_SoDAndLater) {
if (filter->filters[FilterDOT] == 0)
ClientFilters[FilterDOT] = FilterShow;
else if (filter->filters[FilterDOT] == 1)
ClientFilters[FilterDOT] = FilterShowSelfOnly;
else if (filter->filters[FilterDOT] == 2)
ClientFilters[FilterDOT] = FilterShowGroupOnly;
else
ClientFilters[FilterDOT] = FilterHide;
} else {
if (filter->filters[FilterDOT] == 0) // show functions as self only
ClientFilters[FilterDOT] = FilterShowSelfOnly;
else
ClientFilters[FilterDOT] = FilterHide;
}
2013-02-16 16:14:39 -08:00
Filter1(FilterPetHits);
Filter1(FilterPetMisses);
Filter1(FilterFocusEffects);
Filter1(FilterPetSpells);
if (GetClientVersionBit() & BIT_SoDAndLater) {
if (filter->filters[FilterHealOverTime] == 0)
ClientFilters[FilterHealOverTime] = FilterShow;
// This is called 'Show Mine Only' in the clients, but functions the same as show
// so instead of apply special logic, just set to show
else if (filter->filters[FilterHealOverTime] == 1)
ClientFilters[FilterHealOverTime] = FilterShow;
else
ClientFilters[FilterHealOverTime] = FilterHide;
} else {
// these clients don't have a 'self only' filter
Filter1(FilterHealOverTime);
}
2013-02-16 16:14:39 -08:00
}
// 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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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.
//
2013-05-09 10:44:08 -04:00
void Client::Message_StringID(uint32 type, uint32 string_id, const char* message1,
2013-02-16 16:14:39 -08:00
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;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(distance>0)
entity_list.QueueCloseClients(this,outapp,false,distance);
else
QueuePacket(outapp);
safe_delete(outapp);
}
// helper function, returns true if we should see the message
bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter)
{
eqFilterMode mode = GetFilter(filter);
// easy ones first
if (mode == FilterShow)
return true;
else if (mode == FilterHide)
return false;
if (!sender && mode == FilterHide) {
return false;
} else if (sender) {
if (this == sender) {
if (mode == FilterHide) // don't need to check others
return false;
} else if (mode == FilterShowSelfOnly) { // we know sender isn't us
return false;
} else if (mode == FilterShowGroupOnly) {
Group *g = GetGroup();
Raid *r = GetRaid();
if (g) {
if (g->IsGroupMember(sender))
return true;
} else if (r && sender->IsClient()) {
uint32 rgid1 = r->GetGroup(this);
uint32 rgid2 = r->GetGroup(sender->CastToClient());
if (rgid1 != 0xFFFFFFFF && rgid1 == rgid2)
return true;
} else {
return false;
}
}
}
// we passed our checks
return true;
}
void Client::FilteredMessage_StringID(Mob *sender, uint32 type,
eqFilterType filter, uint32 string_id)
{
if (!FilteredMessageCheck(sender, filter))
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;
QueuePacket(outapp);
safe_delete(outapp);
return;
}
void Client::FilteredMessage_StringID(Mob *sender, uint32 type, eqFilterType filter, 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)
{
if (!FilteredMessageCheck(sender, filter))
return;
int i, argcount, length;
char *bufptr;
const char *message_arg[9] = {0};
if (type == MT_Emote)
type = 4;
if (!message1) {
FilteredMessage_StringID(sender, type, filter, 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;
}
QueuePacket(outapp);
safe_delete(outapp);
}
2013-02-16 16:14:39 -08:00
void Client::Tell_StringID(uint32 string_id, const char *who, const char *message)
{
char string_id_str[10];
snprintf(string_id_str, 10, "%d", string_id);
Message_StringID(MT_TellEcho, TELL_QUEUED_MESSAGE, who, string_id_str, message);
}
2013-02-16 16:14:39 -08:00
void Client::SetTint(int16 in_slot, uint32 color) {
Color_Struct new_color;
new_color.color = color;
SetTint(in_slot, new_color);
2014-08-31 21:31:44 -05:00
database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color);
2013-02-16 16:14:39 -08:00
}
// Still need to reconcile bracer01 versus bracer02
void Client::SetTint(int16 in_slot, Color_Struct& color) {
if (in_slot==MainHead)
m_pp.item_tint[MaterialHead].color=color.color;
else if (in_slot==MainArms)
m_pp.item_tint[MaterialArms].color=color.color;
else if (in_slot==MainWrist1)
m_pp.item_tint[MaterialWrist].color=color.color;
/*
// non-live behavior
2013-02-16 16:14:39 -08:00
else if (in_slot==SLOT_BRACER02)
m_pp.item_tint[MaterialWrist].color=color.color;
*/
else if (in_slot==MainHands)
m_pp.item_tint[MaterialHands].color=color.color;
else if (in_slot==MainPrimary)
m_pp.item_tint[MaterialPrimary].color=color.color;
else if (in_slot==MainSecondary)
m_pp.item_tint[MaterialSecondary].color=color.color;
else if (in_slot==MainChest)
m_pp.item_tint[MaterialChest].color=color.color;
else if (in_slot==MainLegs)
m_pp.item_tint[MaterialLegs].color=color.color;
else if (in_slot==MainFeet)
m_pp.item_tint[MaterialFeet].color=color.color;
2014-08-31 21:31:44 -05:00
database.SaveCharacterMaterialColor(this->CharacterID(), in_slot, color.color);
2013-02-16 16:14:39 -08:00
}
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;
State of Commit: Testable if you ask me (Akkadius) what you need to do - Need to convert a list of functions and columns and should be ready to start intensive testing phase - All preliminary tests show things working great - All of player profile is saved and loaded from the database - DBAsync has been completely removed from all code - Removed zone/dbasync.cpp/.h - Removed common/dbasync.cpp/.h - Removed dbasync from cmake commmon and zone - Cleaned up a ton of functions - Added several tables to world CheckDatabaseConversions script: - `character_skills` - `character_languages` - `character_bind` - `character_alternate_abilities` - `character_currency` - `character_data` - `character_spells` - `character_memmed_spells` - `character_disciplines` - `character_material` - `character_tribute` - `character_bandolier` - `character_potionbelt` - Character select now loads from `character_data` - Character creation now creates to `character_data` - Updated function Database::UpdateName to use `character_data` - Updated function Database::CheckUsedName to use `character_data` - Updated function Database::MoveCharacterToZone to use `character_data` - Updated function Database::SetLoginFlags to use `character_data` - Updated function Database::SetFirstLogon to use `character_data` - Updated function Database::SetLFG to use `character_data` - Removed CopyCharacter functions and commands, to be recreated later since it never worked to begin with - Removed SharedDatabase::SetPlayerProfile - Trimmed down redundant case switch statements for World sendpackets to QueryServ - Added Character Methods to Database class: Loads: bool LoadCharacterBandolier(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool LoadCharacterPotions(uint32 character_id, PlayerProfile_Struct* pp); Saves: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, float x, float y, float z, float heading, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); bool SaveCharacterSpellSwap(uint32 character_id, uint32 spell_id, uint32 from_slot, uint32 to_slot); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); bool SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); bool SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); bool SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); bool SaveCharacterTribute(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterBandolier(uint32 character_id, uint8 bandolier_id, uint8 bandolier_slot, uint32 item_id, uint32 icon, const char* bandolier_name); bool SaveCharacterPotionBelt(uint32 character_id, uint8 potion_id, uint32 item_id, uint32 icon); Deletes: bool DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool DeleteCharacterDisc(uint32 character_id, uint32 slot_id); bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);
2014-09-04 07:24:17 -05:00
database.SaveCharacterLanguage(this->CharacterID(), langid, value);
2013-02-16 16:14:39 -08:00
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; // why are we returning MainCharm instead of INVALID_INDEX? (must be a pre-charm segment...)
2013-02-16 16:14:39 -08:00
if(bracer)
return MainWrist2;
2013-02-16 16:14:39 -08:00
switch(slot){
case MaterialHead:
slot2=MainHead;
2013-02-16 16:14:39 -08:00
break;
case MaterialChest:
slot2=MainChest;
2013-02-16 16:14:39 -08:00
break;
case MaterialArms:
slot2=MainArms;
2013-02-16 16:14:39 -08:00
break;
case MaterialWrist:
slot2=MainWrist1;
2013-02-16 16:14:39 -08:00
break;
case MaterialHands:
slot2=MainHands;
2013-02-16 16:14:39 -08:00
break;
case MaterialLegs:
slot2=MainLegs;
2013-02-16 16:14:39 -08:00
break;
case MaterialFeet:
slot2=MainFeet;
2013-02-16 16:14:39 -08:00
break;
}
return slot2;
}
uint8 Client::SlotConvert2(uint8 slot){
uint8 slot2=0; // same as above...
2013-02-16 16:14:39 -08:00
switch(slot){
case MainHead:
slot2=MaterialHead;
2013-02-16 16:14:39 -08:00
break;
case MainChest:
slot2=MaterialChest;
2013-02-16 16:14:39 -08:00
break;
case MainArms:
slot2=MaterialArms;
2013-02-16 16:14:39 -08:00
break;
case MainWrist1:
slot2=MaterialWrist;
2013-02-16 16:14:39 -08:00
break;
case MainHands:
slot2=MaterialHands;
2013-02-16 16:14:39 -08:00
break;
case MainLegs:
slot2=MaterialLegs;
2013-02-16 16:14:39 -08:00
break;
case MainFeet:
slot2=MaterialFeet;
2013-02-16 16:14:39 -08:00
break;
}
return slot2;
}
void Client::Escape()
{
2013-05-09 10:44:08 -04:00
entity_list.RemoveFromTargets(this, true);
SetInvisible(1);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
Message_StringID(MT_Skills, ESCAPE);
2013-02-16 16:14:39 -08:00
}
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.
{
2013-05-06 13:07:41 -04:00
if (GetCHA() > 103)
2013-02-16 16:14:39 -08:00
{
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.
2013-05-06 13:07:41 -04:00
if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct)))
2013-02-16 16:14:39 -08:00
chaformula = 1*(RuleI(Merchant, PricePenaltyPct));
}
}
if (factionlvl <= FACTION_INDIFFERENT) // Indifferent or better.
{
2013-05-06 13:07:41 -04:00
if (GetCHA() > 75)
2013-02-16 16:14:39 -08:00
{
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));
}
2013-05-06 13:07:41 -04:00
else if (GetCHA() < 75)
2013-02-16 16:14:39 -08:00
{
chaformula = (75 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // Faction modifier keeps up from reaching bottom price penalty.
2013-05-06 13:07:41 -04:00
if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct)))
2013-02-16 16:14:39 -08:00
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) {
2013-05-04 18:06:58 -07:00
if(SQL_log != nullptr) {
2013-02-16 16:14:39 -08:00
fclose(SQL_log);
2013-05-04 18:06:58 -07:00
SQL_log = nullptr;
2013-02-16 16:14:39 -08:00
}
2013-05-04 18:06:58 -07:00
if(file != nullptr) {
if(strstr(file, "..") != nullptr) {
2013-02-16 16:14:39 -08:00
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");
2013-05-04 18:06:58 -07:00
if(SQL_log == nullptr) {
2013-02-16 16:14:39 -08:00
Message(13, "Unable to open SQL log file: %s\n", strerror(errno));
}
}
}
void Client::LogSQL(const char *fmt, ...) {
2013-05-04 18:06:58 -07:00
if(SQL_log == nullptr)
2013-02-16 16:14:39 -08:00
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");
2013-05-04 18:06:58 -07:00
while(ptr != nullptr)
2013-02-16 16:14:39 -08:00
{
client->Message(0,"%s",ptr);
2013-05-04 18:06:58 -07:00
ptr = strtok(nullptr, "\n");
2013-02-16 16:14:39 -08:00
}
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;
2013-05-09 10:44:08 -04:00
if((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) {
2013-02-16 16:14:39 -08:00
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;
2013-05-04 18:06:58 -07:00
TranslocateTime = time(nullptr);
2013-02-16 16:14:39 -08:00
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(SkillPickPockets);
2013-02-16 16:14:39 -08:00
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)) ||
2013-05-09 10:44:08 -04:00
(strlen(Text) > (sizeof(olms->Text)-1))) return;
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(strlen(Text) > (sizeof(olms->Text)-1))
return;
2013-05-06 13:07:41 -04:00
if(!target)
2013-02-16 16:14:39 -08:00
title_type = 0;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
olms->PopupID = PopupID;
olms->NegativeID = NegativeID;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
FastQueuePacket(&app);
}
void Client::KeyRingLoad()
{
2014-08-23 22:46:29 -07:00
std::string query = StringFormat("SELECT item_id FROM keyring "
"WHERE char_id = '%i' ORDER BY item_id", character_id);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
std::cerr << "Error in Client::KeyRingLoad query '" << query << "' " << results.ErrorMessage() << std::endl;
2013-02-16 16:14:39 -08:00
return;
2014-08-23 22:46:29 -07:00
}
for (auto row = results.begin(); row != results.end(); ++row)
keyring.push_back(atoi(row[0]));
2013-02-16 16:14:39 -08:00
}
void Client::KeyRingAdd(uint32 item_id)
{
2014-08-23 22:49:27 -07:00
if(0==item_id)
return;
bool found = KeyRingCheck(item_id);
if (found)
return;
std::string query = StringFormat("INSERT INTO keyring(char_id, item_id) VALUES(%i, %i)", character_id, item_id);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
std::cerr << "Error in Doors::HandleClick query '" << query << "' " << results.ErrorMessage() << std::endl;
return;
}
Message(4,"Added to keyring.");
keyring.push_back(item_id);
2013-02-16 16:14:39 -08:00
}
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)
{
2013-05-04 18:06:58 -07:00
if ((item = database.GetItem(*iter))!=nullptr) {
2013-02-16 16:14:39 -08:00
Message(4,item->Name);
}
}
}
bool Client::IsDiscovered(uint32 itemid) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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
{
std::cerr << "Error in IsDiscovered query '" << query << "' " << errbuf << std::endl;
2013-02-16 16:14:39 -08:00
}
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
2013-05-06 13:07:41 -04:00
// We don't fill in the class, level or zone, because we may not be able to determine
2013-02-16 16:14:39 -08:00
// 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()
{
SkillUseTypes skill = HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill
bool equiped = m_inv.GetItem(MainPrimary);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if (!equiped)
skill = SkillHandtoHand;
2013-02-16 16:14:39 -08:00
else {
uint8 type = m_inv.GetItem(MainPrimary)->GetItem()->ItemType; //is this the best way to do this?
2013-02-16 16:14:39 -08:00
switch (type)
{
2013-10-27 09:04:52 -04:00
case ItemType1HSlash: // 1H Slashing
2013-02-16 16:14:39 -08:00
{
skill = Skill1HSlashing;
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemType2HSlash: // 2H Slashing
2013-02-16 16:14:39 -08:00
{
skill = Skill2HSlashing;
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemType1HPiercing: // Piercing
2013-02-16 16:14:39 -08:00
{
skill = Skill1HPiercing;
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemType1HBlunt: // 1H Blunt
2013-02-16 16:14:39 -08:00
{
skill = Skill1HBlunt;
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemType2HBlunt: // 2H Blunt
2013-02-16 16:14:39 -08:00
{
skill = Skill2HBlunt;
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemType2HPiercing: // 2H Piercing
2013-02-16 16:14:39 -08:00
{
skill = Skill1HPiercing; // change to Skill2HPiercing once activated
2013-02-16 16:14:39 -08:00
break;
}
2013-10-27 09:04:52 -04:00
case ItemTypeMartial: // Hand to Hand
2013-02-16 16:14:39 -08:00
{
skill = SkillHandtoHand;
2013-02-16 16:14:39 -08:00
break;
}
default: // All other types default to Hand to Hand
{
skill = SkillHandtoHand;
2013-02-16 16:14:39 -08:00
break;
}
}
}
return GetSkill(skill);
}
uint16 Client::GetTotalATK()
{
uint16 AttackRating = 0;
uint16 WornCap = itembonuses.ATK;
if(IsClient()) {
AttackRating = ((WornCap * 1.342) + (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69));
2013-02-16 16:14:39 -08:00
AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement();
if (AttackRating < 10)
AttackRating = 10;
}
else
AttackRating = GetATK();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
AttackRating += spellbonuses.ATK;
return AttackRating;
}
uint16 Client::GetATKRating()
{
uint16 AttackRating = 0;
if(IsClient()) {
AttackRating = (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69);
2013-02-16 16:14:39 -08:00
if (AttackRating < 10)
AttackRating = 10;
}
return AttackRating;
}
void Client::VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber) {
uint32 GroupOrRaidID = 0;
switch(Type) {
2013-05-06 13:07:41 -04:00
case VoiceMacroGroup: {
2013-02-16 16:14:39 -08:00
Group* g = GetGroup();
if(g)
GroupOrRaidID = g->GetID();
else
return;
break;
}
2013-05-06 13:07:41 -04:00
case VoiceMacroRaid: {
2013-02-16 16:14:39 -08:00
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() {
2013-05-09 10:44:08 -04:00
for(unsigned int i = 0; i < MAX_GROUP_LEADERSHIP_AA_ARRAY; i++)
2013-02-16 16:14:39 -08:00
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();
database.SaveCharacterLeadershipAA(this->CharacterID(), &m_pp);
2013-02-16 16:14:39 -08:00
}
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; }
2013-02-16 16:14:39 -08:00
}
SendLeadershipEXPUpdate();
}
bool Client::IsLeadershipEXPOn() {
2013-02-16 16:14:39 -08:00
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;
2014-08-12 15:18:11 -07:00
2013-02-16 16:14:39 -08:00
// 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;
2014-08-12 15:18:11 -07:00
// Pause the rest timer
if (AggroCount == 1)
SavedRaidRestTimer = rest_timer.GetRemainingTime();
2013-02-16 16:14:39 -08:00
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.
//
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// 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;
2014-08-12 15:18:11 -07:00
uint32 time_until_rest;
if (GetEngagedRaidTarget()) {
time_until_rest = RuleI(Character, RestRegenRaidTimeToActivate) * 1000;
SetEngagedRaidTarget(false);
} else {
if (SavedRaidRestTimer > (RuleI(Character, RestRegenTimeToActivate) * 1000)) {
time_until_rest = SavedRaidRestTimer;
SavedRaidRestTimer = 0;
} else {
time_until_rest = RuleI(Character, RestRegenTimeToActivate) * 1000;
}
}
rest_timer.Start(time_until_rest);
2013-02-16 16:14:39 -08:00
if(GetClientVersion() >= EQClientSoF) {
EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 5);
char *Buffer = (char *)outapp->pBuffer;
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x00);
2014-08-12 15:18:11 -07:00
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, (uint32)(time_until_rest / 1000));
2013-02-16 16:14:39 -08:00
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
//
//If no options have been given, default to Bind + Rez
if (respawn_options.empty())
{
BindStruct* b = &m_pp.binds[0];
RespawnOption opt;
opt.name = "Bind Location";
opt.zoneid = b->zoneId;
opt.x = b->x;
opt.y = b->y;
opt.z = b->z;
opt.heading = b->heading;
respawn_options.push_front(opt);
}
//Rez is always added at the end
RespawnOption rez;
rez.name = "Resurrect";
rez.zoneid = zone->GetZoneID();
rez.x = GetX();
rez.y = GetY();
rez.z = GetZ();
rez.heading = GetHeading();
respawn_options.push_back(rez);
int num_options = respawn_options.size();
uint32 PacketLength = 17 + (26 * num_options); //Header size + per-option invariant size
std::list<RespawnOption>::iterator itr;
RespawnOption* opt;
2013-02-16 16:14:39 -08:00
//Find string size for each option
for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr)
{
opt = &(*itr);
PacketLength += opt->name.size() + 1; //+1 for cstring
}
2013-02-16 16:14:39 -08:00
EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength);
char* buffer = (char*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
//Packet header
VARSTRUCT_ENCODE_TYPE(uint32, buffer, initial_respawn_selection); //initial selection (from 0)
VARSTRUCT_ENCODE_TYPE(uint32, buffer, RuleI(Character, RespawnFromHoverTimer) * 1000);
VARSTRUCT_ENCODE_TYPE(uint32, buffer, 0); //unknown
VARSTRUCT_ENCODE_TYPE(uint32, buffer, num_options); //number of options to display
2013-02-16 16:14:39 -08:00
//Individual options
int count = 0;
for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr)
{
opt = &(*itr);
VARSTRUCT_ENCODE_TYPE(uint32, buffer, count++); //option num (from 0)
VARSTRUCT_ENCODE_TYPE(uint32, buffer, opt->zoneid);
VARSTRUCT_ENCODE_TYPE(float, buffer, opt->x);
VARSTRUCT_ENCODE_TYPE(float, buffer, opt->y);
VARSTRUCT_ENCODE_TYPE(float, buffer, opt->z);
VARSTRUCT_ENCODE_TYPE(float, buffer, opt->heading);
VARSTRUCT_ENCODE_STRING(buffer, opt->name.c_str());
VARSTRUCT_ENCODE_TYPE(uint8, buffer, (count == num_options)); //is this one Rez (the last option)?
}
2013-02-16 16:14:39 -08:00
QueuePacket(outapp);
safe_delete(outapp);
return;
}
void Client::HandleLDoNOpen(NPC *target)
{
if(target)
{
if(target->GetClass() != LDON_TREASURE)
{
2013-05-06 13:07:41 -04:00
LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was not a treasure chest.",
2013-02-16 16:14:39 -08:00
GetName(), target->GetName(), target->GetName());
return;
}
if(DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse))
{
2013-05-06 13:07:41 -04:00
LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was out of range",
2013-02-16 16:14:39 -08:00
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);
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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, SkillHandtoHand);
2013-02-16 16:14:39 -08:00
}
}
}
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:
2013-05-09 10:44:08 -04:00
Message_StringID(MT_Skills, LDON_DONT_KNOW_TRAPPED, target->GetCleanName());
2013-02-16 16:14:39 -08:00
break;
case 1:
2013-05-09 10:44:08 -04:00
Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName());
2013-02-16 16:14:39 -08:00
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(),
2013-05-09 10:44:08 -04:00
GetX(), GetY(), GetZ(), GetHeading());
2013-02-16 16:14:39 -08:00
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(),
2013-05-09 10:44:08 -04:00
dest_x, dest_y, dest_z, dest_heading);
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
QueuePacket(outapp);
safe_delete(outapp);
}
2013-05-06 13:07:41 -04:00
void Client::SetStartZone(uint32 zoneid, float x, float y, float z)
2013-02-16 16:14:39 -08:00
{
// setting city to zero allows the player to use /setstartcity to set the city themselves
if(zoneid == 0) {
m_pp.binds[4].zoneId = 0;
2013-05-09 10:44:08 -04:00
this->Message(15,"Your starting city has been reset. Use /setstartcity to choose a new one");
2013-02-16 16:14:39 -08:00
return;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// check to make sure the zone is valid
const char *target_zone_name = database.GetZoneName(zoneid);
2013-05-04 18:06:58 -07:00
if(target_zone_name == nullptr)
2013-02-16 16:14:39 -08:00
return;
m_pp.binds[4].zoneId = zoneid;
2013-05-06 13:07:41 -04:00
if (x == 0 && y == 0 && z ==0)
2013-02-16 16:14:39 -08:00
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;
}
2013-05-06 13:07:41 -04:00
void Client::ShowSkillsWindow()
2013-02-16 16:14:39 -08:00
{
const char *WindowTitle = "Skills";
std::string WindowText;
2013-02-16 16:14:39 -08:00
// using a map for easy alphabetizing of the skills list
std::map<std::string, SkillUseTypes> Skills;
std::map<std::string, SkillUseTypes>::iterator it;
2013-02-16 16:14:39 -08:00
// 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]] = (SkillUseTypes)i;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// print out all available skills
for(it = Skills.begin(); it != Skills.end(); ++it) {
2013-02-16 16:14:39 -08:00
if(GetSkill(it->second) > 0 || MaxSkill(it->second) > 0) {
WindowText += it->first;
// line up the values
for (int j = 0; j < EmuConstants::ITEM_COMMON_SIZE; j++)
2013-02-16 16:14:39 -08:00
WindowText += "&nbsp;";
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());
}
2013-05-06 13:07:41 -04:00
void Client::SetShadowStepExemption(bool v)
2013-02-16 16:14:39 -08:00
{
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())
{
2013-05-06 13:07:41 -04:00
//still potential to trigger this if you're knocked back off a
2013-02-16 16:14:39 -08:00
//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;
}
2013-05-06 13:07:41 -04:00
m_ShadowStepExemption = v;
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
void Client::SetKnockBackExemption(bool v)
2013-02-16 16:14:39 -08:00
{
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())
{
2013-05-06 13:07:41 -04:00
//still potential to trigger this if you're knocked back off a
2013-02-16 16:14:39 -08:00
//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;
}
2013-05-06 13:07:41 -04:00
m_KnockBackExemption = v;
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
void Client::SetPortExemption(bool v)
2013-02-16 16:14:39 -08:00
{
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())
{
2013-05-06 13:07:41 -04:00
//still potential to trigger this if you're knocked back off a
2013-02-16 16:14:39 -08:00
//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;
}
2013-05-06 13:07:41 -04:00
m_PortExemption = v;
2013-02-16 16:14:39 -08:00
}
void Client::Signal(uint32 data)
{
char buf[32];
snprintf(buf, 31, "%d", data);
buf[31] = '\0';
2013-05-09 10:44:08 -04:00
parse->EventPlayer(EVENT_SIGNAL, this, buf, 0);
2013-02-16 16:14:39 -08:00
}
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:
{
2013-05-06 13:07:41 -04:00
//The portals are so common in paineel/felwitheb that checking
//distances wouldn't be worth it cause unless you're porting to the
2013-02-16 16:14:39 -08:00
//start field you're going to be triggering this and that's a level of
//accuracy I'm willing to sacrifice
return true;
break;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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()),
2013-05-06 13:07:41 -04:00
errbuf,&result))
2013-02-16 16:14:39 -08:00
{
2013-05-06 13:07:41 -04:00
while((row = mysql_fetch_row(result)))
2013-02-16 16:14:39 -08:00
{
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;
std::list<InternalVeteranReward>::iterator iter = zone->VeteranRewards.begin();
2013-02-16 16:14:39 -08:00
while(iter != zone->VeteranRewards.end())
{
if((*iter).claim_id == rewards[i].id)
{
break;
}
++iter;
2013-02-16 16:14:39 -08:00
}
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 = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; ++i)
2013-02-16 16:14:39 -08:00
{
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),
2013-05-06 13:07:41 -04:00
errbuf,&result))
2013-02-16 16:14:39 -08:00
{
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;
}
std::list<InternalVeteranReward>::iterator iter = zone->VeteranRewards.begin();
2013-02-16 16:14:39 -08:00
while(iter != zone->VeteranRewards.end())
{
if((*iter).claim_id == claim_id)
{
break;
}
++iter;
2013-02-16 16:14:39 -08:00
}
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),
2013-05-06 13:07:41 -04:00
errbuf))
2013-02-16 16:14:39 -08:00
{
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),
2013-05-06 13:07:41 -04:00
errbuf))
2013-02-16 16:14:39 -08:00
{
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()
{
2013-05-09 10:44:08 -04:00
NPC *CurrentPet = GetPet()->CastToNPC();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
int AALevel = GetAA(aaSuspendedMinion);
if(AALevel == 0)
return;
if(GetLevel() < 62)
return;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!CurrentPet)
{
if(m_suspendedminion.SpellID > 0)
{
MakePoweredPet(m_suspendedminion.SpellID, spells[m_suspendedminion.SpellID].teleport_zone,
m_suspendedminion.petpower, m_suspendedminion.Name, m_suspendedminion.size);
2013-02-16 16:14:39 -08:00
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());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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();
m_suspendedminion.size = CurrentPet->GetSize();
2013-02-16 16:14:39 -08:00
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;
SaveCurrency();
2013-02-16 16:14:39 -08:00
SendCrystalCounts();
}
// Processes a client request to inspect a SoF+ client's equipment.
2013-02-16 16:14:39 -08:00
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();
2013-05-04 18:06:58 -07:00
const Item_Struct* item = nullptr;
const ItemInst* inst = nullptr;
2013-02-16 16:14:39 -08:00
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(MainPowerSource);
2013-02-16 16:14:39 -08:00
if(inst) {
item = inst->GetItem();
if(item) {
// we shouldn't do this..but, that's the way it's coded atm...
// (this type of action should be handled exclusively in the client translator)
strcpy(insr->itemnames[SoF::slots::MainPowerSource], item->Name);
insr->itemicons[SoF::slots::MainPowerSource] = item->Icon;
2013-02-16 16:14:39 -08:00
}
else
insr->itemicons[SoF::slots::MainPowerSource] = 0xFFFFFFFF;
2013-02-16 16:14:39 -08:00
}
inst = requestee->GetInv().GetItem(MainAmmo);
2013-02-16 16:14:39 -08:00
if(inst) {
item = inst->GetItem();
if(item) {
strcpy(insr->itemnames[SoF::slots::MainAmmo], item->Name);
insr->itemicons[SoF::slots::MainAmmo] = item->Icon;
2013-02-16 16:14:39 -08:00
}
else
insr->itemicons[SoF::slots::MainAmmo] = 0xFFFFFFFF;
2013-02-16 16:14:39 -08:00
}
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
2013-05-09 10:44:08 -04:00
safe_delete(outapp);
2013-02-16 16:14:39 -08:00
}
}
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
2013-05-06 13:07:41 -04:00
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Member 0
2013-02-16 16:14:39 -08:00
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
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(ad->time_to_enter != 0)
{
arr->timetoenter = ad->time_to_enter;
}
else
{
arr->timeleft = ad->time_left;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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"
2013-05-06 13:07:41 -04:00
" I can rest easy. Please help me find my way out of here as soon as you can"
2013-02-16 16:14:39 -08:00
" I'll stay close behind you!");
}
void Client::CheckEmoteHail(Mob *target, const char* message)
{
if(
(message[0] != 'H' &&
message[0] != 'h') ||
2013-05-06 13:07:41 -04:00
message[1] != 'a' ||
message[2] != 'i' ||
2013-02-16 16:14:39 -08:00
message[3] != 'l'){
return;
}
if(!target || !target->IsNPC())
{
return;
}
if(target->GetOwnerID() != 0)
{
return;
}
uint16 emoteid = target->GetEmoteID();
2013-02-16 16:14:39 -08:00
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()
{
2013-05-04 18:06:58 -07:00
Corpse *ClosestCorpse = nullptr;
2013-02-16 16:14:39 -08:00
if(!GetTarget())
2013-05-04 18:06:58 -07:00
ClosestCorpse = entity_list.GetClosestCorpse(this, nullptr);
2013-02-16 16:14:39 -08:00
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(uint16 CorpseID)
2013-02-16 16:14:39 -08:00
{
for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) {
if (It->second == CorpseID)
2013-02-16 16:14:39 -08:00
return true;
}
return false;
}
void Client::DragCorpses()
{
for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) {
Mob *corpse = entity_list.GetMob(It->second);
2013-05-06 13:07:41 -04:00
if (corpse && corpse->IsPlayerCorpse() &&
(DistNoRootNoZ(*corpse) <= RuleR(Character, DragCorpseDistance)))
2013-02-16 16:14:39 -08:00
continue;
if (!corpse || !corpse->IsPlayerCorpse() ||
corpse->CastToCorpse()->IsBeingLooted() ||
!corpse->CastToCorpse()->Summon(this, false, false)) {
2013-02-16 16:14:39 -08:00
Message_StringID(MT_DefaultText, CORPSEDRAG_STOP);
It = DraggedCorpses.erase(It);
2013-02-16 16:14:39 -08:00
}
}
}
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
AA_SwarmPet pet;
pet.count = pet_count;
pet.duration = pet_duration;
pet.npc_id = record.npc_type;
2013-05-04 18:06:58 -07:00
NPCType *made_npc = nullptr;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
const NPCType *npc_type = database.GetNPCType(pet.npc_id);
2013-05-04 18:06:58 -07:00
if(npc_type == nullptr) {
2013-02-16 16:14:39 -08:00
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
2013-05-06 13:07:41 -04:00
made_npc = new NPCType;
2013-02-16 16:14:39 -08:00
memcpy(made_npc, npc_type, sizeof(NPCType));
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
strcpy(made_npc->name, name_override);
2013-05-06 13:07:41 -04:00
made_npc->level = GetLevel();
2013-02-16 16:14:39 -08:00
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(MaterialChest);
made_npc->helmtexture = GetEquipmentMaterial(MaterialHead);
2013-02-16 16:14:39 -08:00
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(MaterialPrimary);
made_npc->d_meele_texture2 = GetEquipmentMaterial(MaterialSecondary);
for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_END; i++) {
2013-02-16 16:14:39 -08:00
made_npc->armor_tint[i] = GetEquipmentColor(i);
}
made_npc->loottable_id = 0;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
npc_type = made_npc;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
int summon_count = 0;
summon_count = pet.count;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(summon_count > MAX_SWARM_PETS)
summon_count = MAX_SWARM_PETS;
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
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 };
2013-02-16 16:14:39 -08:00
TempPets(true);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
while(summon_count > 0) {
2013-05-04 18:06:58 -07:00
NPCType *npc_dup = nullptr;
if(made_npc != nullptr) {
2013-02-16 16:14:39 -08:00
npc_dup = new NPCType;
memcpy(npc_dup, made_npc, sizeof(NPCType));
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
NPC* npca = new NPC(
2013-05-04 18:06:58 -07:00
(npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer
2013-05-06 13:07:41 -04:00
0,
GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count],
2013-02-16 16:14:39 -08:00
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();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
//we allocated a new NPC type object, give the NPC ownership of that memory
2013-05-04 18:06:58 -07:00
if(npc_dup != nullptr)
2013-02-16 16:14:39 -08:00
npca->GiveNPCTypeData(npc_dup);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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 = "&nbsp;";
std::string indS = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
std::string indM = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
std::string indL = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
std::string div = " | ";
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
std::string color_red = "<c \"#993333\">";
std::string color_blue = "<c \"#9999FF\">";
2013-05-09 10:44:08 -04:00
std::string color_green = "<c \"#33FF99\">";
std::string bright_green = "<c \"#7CFC00\">";
std::string bright_red = "<c \"#FF0000\">";
2013-02-16 16:14:39 -08:00
std::string heroic_color = "<c \"#d6b228\"> +";
2013-05-06 13:07:41 -04:00
// Set Class
2013-02-16 16:14:39 -08:00
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" };
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(GetClass() < 17 && GetClass() > 0) { class_Name = class_List[GetClass()-1]; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// Race
std::string race_Name = itoa(GetRace());
2013-05-06 13:07:41 -04:00
switch(GetRace())
2013-02-16 16:14:39 -08:00
{
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;
}
2013-05-09 10:44:08 -04:00
/*##########################################################
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2013-05-09 10:44:08 -04:00
H/M/E String
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
##########################################################*/
std::string HME_row = "";
2013-05-09 10:44:08 -04:00
//Loop Variables
/*===========================*/
std::string cur_field = "";
std::string total_field = "";
std::string cur_name = "";
2013-02-16 16:14:39 -08:00
std::string cur_spacing = "";
std::string cur_color = "";
2013-05-09 10:44:08 -04:00
int hme_rows = 3; // Rows in display
2013-02-16 16:14:39 -08:00
int max_HME_value_len = 9; // 9 digits in the displayed value
2013-05-06 13:07:41 -04:00
for(int hme_row_counter = 0; hme_row_counter < hme_rows; hme_row_counter++)
2013-02-16 16:14:39 -08:00
{
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; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
cur_spacing.clear();
for(int a = cur_field.size(); a < max_HME_value_len; a++) { cur_spacing += " ."; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
HME_row += indM + cur_name + cur_spacing + cur_color + cur_field + "</c> / " + total_field + "<br>";
}
2013-05-09 10:44:08 -04:00
/*##########################################################
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2013-05-09 10:44:08 -04:00
Regen String
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
##########################################################*/
std::string regen_string;
2013-05-09 10:44:08 -04:00
//Loop Variables
/*===========================*/
2013-02-16 16:14:39 -08:00
std::string regen_row_header = "";
std::string regen_row_color = "";
2013-05-09 10:44:08 -04:00
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 = "";
2013-02-16 16:14:39 -08:00
std::string aa_regen_spacing = "";
2013-05-09 10:44:08 -04:00
std::string total_regen_field = "";
int regen_rows = 3; // Number of rows
2013-02-16 16:14:39 -08:00
int max_regen_value_len = 5; // 5 digits in the displayed value(larger values will not get cut off, this is just a baseline)
2013-05-06 13:07:41 -04:00
for(int regen_row_counter = 0; regen_row_counter < regen_rows; regen_row_counter++)
2013-02-16 16:14:39 -08:00
{
switch(regen_row_counter)
{
case 0: {
regen_row_header = "H: ";
regen_row_color = color_red;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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; }
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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 += " ."; }
2013-05-06 13:07:41 -04:00
for(int b = spell_regen_field.size(); b < max_regen_value_len; b++) { spell_regen_spacing += " ."; }
2013-02-16 16:14:39 -08:00
for(int b = aa_regen_field.size(); b < max_regen_value_len; b++) { aa_regen_spacing += " ."; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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>";
}
2013-05-09 10:44:08 -04:00
/*##########################################################
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2013-05-09 10:44:08 -04:00
Stat String
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
##########################################################*/
std::string stat_field = "";
2013-05-09 10:44:08 -04:00
//Loop Variables
/*===========================*/
//first field(stat)
2013-02-16 16:14:39 -08:00
std::string a_stat = "";;
2013-05-09 10:44:08 -04:00
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
2013-02-16 16:14:39 -08:00
int max_stat_value_len = 3; // 3 digits in the displayed value
2013-05-06 13:07:41 -04:00
for(int stat_row_counter = 0; stat_row_counter < stat_rows; stat_row_counter++)
2013-02-16 16:14:39 -08:00
{
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; }
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
a_stat_spacing.clear();
h_stat_spacing.clear();
a_resist_spacing.clear();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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 += " . "; }
2013-05-06 13:07:41 -04:00
stat_field += indP + a_stat_name + a_stat_spacing + a_stat + heroic_color + h_stat + "</c>";
2013-02-16 16:14:39 -08:00
if(stat_row_counter < 6) {
2013-05-06 13:07:41 -04:00
stat_field += h_stat_spacing + a_resist_name + a_resist_spacing + a_resist + heroic_color + h_resist_field + "</c><br>";
2013-02-16 16:14:39 -08:00
}
}
2013-05-09 10:44:08 -04:00
/*##########################################################
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2013-05-09 10:44:08 -04:00
Mod2 String
2013-02-16 16:14:39 -08:00
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
##########################################################*/
std::string mod2_field = "";
2013-05-09 10:44:08 -04:00
//Loop Variables
/*===========================*/
2013-02-16 16:14:39 -08:00
std::string mod2a = "";
2013-05-09 10:44:08 -04:00
std::string mod2a_name = "";
std::string mod2a_spacing = "";
2013-02-16 16:14:39 -08:00
std::string mod2a_cap = "";
std::string mod_row_spacing = "";
std::string mod2b = "";
2013-05-09 10:44:08 -04:00
std::string mod2b_name = "";
std::string mod2b_spacing = "";
2013-02-16 16:14:39 -08:00
std::string mod2b_cap = "";
int mod2a_space_count;
int mod2b_space_count;
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
int mod2_rows = 4;
2013-02-16 16:14:39 -08:00
int max_mod2_value_len = 3; // 3 digits in the displayed value
2013-05-06 13:07:41 -04:00
for(int mod2_row_counter = 0; mod2_row_counter < mod2_rows; mod2_row_counter++)
2013-02-16 16:14:39 -08:00
{
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;
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
mod2a_spacing.clear();
mod_row_spacing.clear();
mod2b_spacing.clear();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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 += " . "; }
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
mod2_field += indP + mod2a_name + mod2a_spacing + mod2a + " / " + mod2a_cap + mod_row_spacing;
mod2_field += mod2b_name + mod2b_spacing + mod2b + " / " + mod2b_cap + "<br>";
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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 (buffs[i].melee_rune > 0) { rune_number += buffs[i].melee_rune; }
2013-02-16 16:14:39 -08:00
if (buffs[i].magic_rune > 0) { magic_rune_number += buffs[i].magic_rune; }
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
int shield_ac = 0;
GetRawACNoShield(shield_ac);
2013-05-06 13:07:41 -04:00
std::string skill_list[] = {
2013-02-16 16:14:39 -08:00
"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",
2013-05-06 13:07:41 -04:00
"Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol_Tolerance","Begging","Jewelry Making","Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy"
2013-02-16 16:14:39 -08:00
};
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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>";
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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>";
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
std::string faction_item_string = "";
char faction_buf[256];
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
for(std::map <uint32, int32>::iterator iter = item_faction_bonuses.begin();
iter != item_faction_bonuses.end();
++iter)
2013-02-16 16:14:39 -08:00
{
memset(&faction_buf, 0, sizeof(faction_buf));
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!database.GetFactionName((int32)((*iter).first), faction_buf, sizeof(faction_buf)))
strcpy(faction_buf, "Not in DB");
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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>";
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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>";
}
2013-05-06 13:07:41 -04:00
std::ostringstream final_string;
final_string <<
/* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast<int>(GetLevel()) << indS << "Race: " << race_Name << "<br>" <<
/* Runes */ indP << "Rune: " << rune_number << indL << indS << "Spell Rune: " << magic_rune_number << "<br>" <<
/* HP/M/E */ HME_row <<
/* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")<br>" <<
/* Atk */ indP << "<c \"#CCFF00\">ATK: " << GetTotalATK() << "</c><br>" <<
/* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "<br>" <<
/* AC */ indP << "<c \"#CCFF00\">AC: " << CalcAC() << "</c><br>" <<
/* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "<br>" <<
/* Haste */ indP << "<c \"#CCFF00\">Haste: " << GetHaste() << "</c><br>" <<
/* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (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: " << GetHealAmt() << " / " << RuleI(Character, ItemHealAmtCap) << "<br>" <<
/* SpellDmg*/ indP << "Spell Dmg: " << GetSpellDmg() << " / " << RuleI(Character, ItemSpellDmgCap) << "<br>" <<
/* Clair */ indP << "Clairvoyance: " << GetClair() << " / " << RuleI(Character, ItemClairvoyanceCap) << "<br>" <<
/* DSMit */ indP << "Dmg Shld Mit: " << GetDSMit() << " / " << RuleI(Character, ItemDSMitigationCap) << "<br><br>";
2013-05-06 13:07:41 -04:00
if(GetClass() == BARD)
final_string << bard_info << "<br>";
2013-05-06 13:07:41 -04:00
if(skill_mods.size() > 0)
final_string << skill_mods << "<br>";
2013-02-16 16:14:39 -08:00
if(skill_dmgs.size() > 0)
final_string << skill_dmgs << "<br>";
2013-02-16 16:14:39 -08:00
if(faction_item_string.size() > 0)
final_string << faction_item_string;
2013-05-06 13:07:41 -04:00
std::string final_stats = final_string.str();
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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);
2013-05-06 13:07:41 -04:00
if(CalcMaxMana() > 0)
2013-02-16 16:14:39 -08:00
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:
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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() {
2013-05-09 10:44:08 -04:00
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;
std::list<AltCurrencyDefinition_Struct>::iterator iter = zone->AlternateCurrencies.begin();
2013-05-09 10:44:08 -04:00
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;
2013-05-09 10:44:08 -04:00
}
FastQueuePacket(&outapp);
}
2013-02-16 16:14:39 -08:00
}
void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount)
{
2013-05-09 10:44:08 -04:00
alternate_currency[currency_id] = new_amount;
database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_amount);
SendAlternateCurrencyValue(currency_id);
2013-02-16 16:14:39 -08:00
}
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method)
2013-02-16 16:14:39 -08:00
{
Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload intensive or metric based logging to a remote server process that could exist on another server entirely Implemented Player Event Logging Types (Go to table `qs_player_events`): 1 = Player_Log_Quest, 2 = Player_Log_Zoning, 3 = Player_Log_Deaths, 4 = Player_Log_Connect_State, 5 = Player_Log_Levels, 6 = Player_Log_Keyring_Addition, 7 = Player_Log_QGlobal_Update, 8 = Player_Log_Task_Updates, 9 = Player_Log_AA_Purchases, 10 = Player_Log_Trade_Skill_Events, 11 = Player_Log_Issued_Commands, 12 = Player_Log_Money_Transactions, 13 = Player_Log_Alternate_Currency_Transactions, - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon Changed all QS Error related logging to 'QUERYSERV__ERROR' (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Added MySQL Tables: - `qs_player_aa_rate_hourly` - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql Added rules for each logging type, source rules here with them enabled by default: - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Spawn related logging cleanup General code cleanup Added queryserv.cpp and queryserv.h with QueryServ class
2014-08-23 23:59:20 -05:00
/* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */
if (method == 1){
/* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */
if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){
std::string event_desc = StringFormat("Added via Quest :: Cursor to Item :: alt_currency_id:%i amount:%i in zoneid:%i instid:%i", currency_id, this->GetZoneID(), this->GetInstanceID());
QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc);
}
}
2013-05-09 10:44:08 -04:00
if(amount == 0) {
return;
}
if(!alternate_currency_loaded) {
alternate_currency_queued_operations.push(std::make_pair(currency_id, amount));
return;
}
2013-05-09 10:44:08 -04:00
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);
2013-02-16 16:14:39 -08:00
}
void Client::SendAlternateCurrencyValues()
{
std::list<AltCurrencyDefinition_Struct>::iterator iter = zone->AlternateCurrencies.begin();
2013-05-09 10:44:08 -04:00
while(iter != zone->AlternateCurrencies.end()) {
SendAlternateCurrencyValue((*iter).id, false);
++iter;
2013-05-09 10:44:08 -04:00
}
2013-02-16 16:14:39 -08:00
}
void Client::SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null)
{
2013-05-09 10:44:08 -04:00
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);
}
2013-02-16 16:14:39 -08:00
}
uint32 Client::GetAlternateCurrencyValue(uint32 currency_id) const
{
2013-05-09 10:44:08 -04:00
std::map<uint32, uint32>::const_iterator iter = alternate_currency.find(currency_id);
if(iter == alternate_currency.end()) {
return 0;
} else {
return (*iter).second;
}
2013-02-16 16:14:39 -08:00
}
void Client::ProcessAlternateCurrencyQueue() {
while(!alternate_currency_queued_operations.empty()) {
std::pair<uint32, int32> op = alternate_currency_queued_operations.front();
AddAlternateCurrencyValue(op.first, op.second);
alternate_currency_queued_operations.pop();
}
}
2013-02-16 16:14:39 -08:00
void Client::OpenLFGuildWindow()
{
EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, 8);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
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;
2013-05-04 18:06:58 -07:00
SendXTargetPacket(i, nullptr);
2013-02-16 16:14:39 -08:00
}
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
{
2013-05-09 10:44:08 -04:00
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)))
2013-02-16 16:14:39 -08:00
{
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)
{
2013-05-09 10:44:08 -04:00
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))
2013-02-16 16:14:39 -08:00
{
XTargets[i].ID = 0;
XTargets[i].Name[0] = 0;
2013-05-04 18:06:58 -07:00
SendXTargetPacket(i, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
void Client::RemoveAutoXTargets()
{
if(!XTargettingAvailable())
return;
for(int i = 0; i < GetMaxXTargets(); ++i)
{
if(XTargets[i].Type == Auto)
{
XTargets[i].ID = 0;
XTargets[i].Name[0] = 0;
SendXTargetPacket(i, nullptr);
2013-02-16 16:14:39 -08:00
}
}
}
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) {
2013-02-16 16:14:39 -08:00
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) {
2013-02-16 16:14:39 -08:00
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)
{
size_t len = strlen(website) + 1;
if(website != 0 && len > 1)
2013-02-16 16:14:39 -08:00
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Weblink, sizeof(Weblink_Struct) + len);
Weblink_Struct *wl = (Weblink_Struct*)outapp->pBuffer;
memcpy(wl->weblink, website, len);
wl->weblink[len] = '\0';
2013-02-16 16:14:39 -08:00
FastQueuePacket(&outapp);
2013-02-16 16:14:39 -08:00
}
}
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!
2013-02-16 16:14:39 -08:00
if (GetClientVersion() >= EQClientRoF)
{
MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID];
2013-02-16 16:14:39 -08:00
if (mercData)
{
2013-03-15 03:27:45 -04:00
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)
2013-03-15 03:27:45 -04:00
{
SendMercMerchantResponsePacket(0);
return;
}
if (mercCount > 0 && mercCount)
{
EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct));
2013-03-15 03:27:45 -04:00
MercenaryDataUpdate_Struct* mdus = (MercenaryDataUpdate_Struct*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
mdus->MercStatus = 0;
mdus->MercCount = mercCount;
2013-03-15 03:27:45 -04:00
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)
2013-02-16 16:14:39 -08:00
{
std::list<MercStanceInfo>::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin();
2013-03-15 03:27:45 -04:00
while(iter != zone->merc_stance_list[mercData->MercTemplateID].end())
2013-02-16 16:14:39 -08:00
{
2013-03-15 03:27:45 -04:00
mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex;
mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID);
stanceindex++;
++iter;
2013-02-16 16:14:39 -08:00
}
2013-03-15 03:27:45 -04:00
}
2013-02-16 16:14:39 -08:00
2013-03-15 03:27:45 -04:00
mdus->MercData[i].MercUnk05 = 1;
FastQueuePacket(&outapp);
return;
2013-02-16 16:14:39 -08:00
}
}
}
else
{
2013-03-15 03:27:45 -04:00
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));
2013-03-15 03:27:45 -04:00
MercenaryMerchantList_Struct* mml = (MercenaryMerchantList_Struct*)outapp->pBuffer;
MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID];
2013-02-16 16:14:39 -08:00
2013-03-15 03:27:45 -04:00
2013-02-16 16:14:39 -08:00
if(mercData)
{
if(mercTypeCount > 0)
{
mml->MercTypeCount = mercTypeCount; //We only should have one merc entry.
mml->MercGrades[i] = 1;
2013-02-16 16:14:39 -08:00
}
mml->MercCount = mercCount;
if(mercCount > 0)
{
2013-02-16 16:14:39 -08:00
mml->Mercs[i].MercID = mercData->MercTemplateID;
mml->Mercs[i].MercType = mercData->MercType;
mml->Mercs[i].MercSubType = mercData->MercSubType;
2013-05-06 13:07:41 -04:00
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;
2013-02-16 16:14:39 -08:00
mml->Mercs[i].AltCurrencyType = altCurrentType;
mml->Mercs[i].MercUnk01 = 0;
mml->Mercs[i].TimeLeft = GetMercInfo().MercTimerRemaining;
2013-02-16 16:14:39 -08:00
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)
{
std::list<MercStanceInfo>::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin();
2013-02-16 16:14:39 -08:00
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;
2013-02-16 16:14:39 -08:00
}
}
FastQueuePacket(&outapp);
}
2013-03-15 03:27:45 -04:00
else
{
safe_delete(outapp);
if (GetClientVersion() == EQClientSoD)
{
SendMercMerchantResponsePacket(0);
}
2013-03-15 03:27:45 -04:00
return;
}
2013-02-16 16:14:39 -08:00
if (GetClientVersion() == EQClientSoD)
{
SendMercMerchantResponsePacket(0);
}
}
2013-03-15 03:27:45 -04:00
else
{
safe_delete(outapp);
if (GetClientVersion() == EQClientSoD)
{
2013-03-15 03:27:45 -04:00
SendMercMerchantResponsePacket(0);
}
2013-03-15 03:27:45 -04:00
return;
}
2013-02-16 16:14:39 -08:00
}
}
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);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
if(!item)
return;
Message_StringID(0, PICK_LORE, item->Name);
}
void Client::GarbleMessage(char *message, uint8 variance)
{
2013-02-17 23:38:51 -05:00
// Garble message by variance%
2013-02-16 16:14:39 -08:00
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);
}
2013-05-06 13:07:41 -04:00
iOther = iOther->GetOwnerOrSelf();
2013-05-06 13:07:41 -04:00
if (iOther->GetPrimaryFaction() < 0)
return GetSpecialFactionCon(iOther);
2013-05-06 13:07:41 -04:00
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--------------------------------------------------------------
2013-05-09 10:44:08 -04:00
//| 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)
2013-05-06 13:07:41 -04:00
{
if (pFaction < 0)
return GetSpecialFactionCon(tnpc);
FACTION_VALUE fac = FACTION_INDIFFERENT;
int32 tmpFactionValue;
FactionMods fmods;
2013-05-09 10:44:08 -04:00
// neotokyo: few optimizations
if (GetFeigned())
return FACTION_INDIFFERENT;
2013-05-09 10:44:08 -04:00
if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead())
return FACTION_INDIFFERENT;
2013-05-09 10:44:08 -04:00
if (IsInvisible(tnpc))
return FACTION_INDIFFERENT;
2013-05-09 10:44:08 -04:00
if (tnpc && tnpc->GetOwnerID() != 0) // pets con amiably to owner and indiff to rest
2013-05-23 20:22:42 -04:00
{
if (char_id == tnpc->GetOwner()->CastToClient()->CharacterID())
return FACTION_AMIABLE;
else
return FACTION_INDIFFERENT;
2013-05-23 20:22:42 -04:00
}
2013-05-09 10:44:08 -04:00
//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
2013-05-09 10:44:08 -04:00
{
return(FACTION_INDIFFERENT);
}
2013-05-09 10:44:08 -04:00
// 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--------------------------------------------------------------
2013-05-09 10:44:08 -04:00
//| Notes: Sets the characters faction standing with the specified NPC.
//o--------------------------------------------------------------
2013-05-09 10:44:08 -04:00
void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity)
{
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;
2013-05-06 13:07:41 -04:00
// 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]);
2013-05-06 13:07:41 -04:00
if(this->itembonuses.HeroicCHA) {
int faction_mod = itembonuses.HeroicCHA / 5;
// If our result isn't truncated, then just do that
2013-05-06 13:07:41 -04:00
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;
2013-05-06 13:07:41 -04:00
// Calculate the faction
2013-05-09 10:44:08 -04:00
if(npc_value[i] != 0) {
tmpValue = current_value + mod + npc_value[i];
int16 FactionModPct = spellbonuses.FactionModPct + itembonuses.FactionModPct + aabonuses.FactionModPct;
tmpValue += (tmpValue * FactionModPct) / 100;
2013-05-09 10:44:08 -04:00
// 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;
SendFactionMessage(npc_value[i], faction_id[i], tmpValue, temp[i]);
2013-05-09 10:44:08 -04:00
}
}
}
return;
}
2013-05-09 10:44:08 -04:00
void Client::SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp)
{
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;
SendFactionMessage(value, faction_id, current_value, temp);
}
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;
2013-05-06 13:07:41 -04:00
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;
}
void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction)
{
int messageid = 0;
int32 tmpFactionValue = 0;
int32 lowestvalue = 0;
FactionMods fmod;
// If a faction is involved, get the data.
if (primaryfaction > 0) {
if (database.GetFactionData(&fmod, GetClass(), GetRace(), GetDeity(), primaryfaction)) {
tmpFactionValue = GetCharacterFactionLevel(primaryfaction);
lowestvalue = std::min(tmpFactionValue, std::min(fmod.class_mod, fmod.race_mod));
}
}
// If no primary faction or biggest influence is your faction hit
if (primaryfaction <= 0 || lowestvalue == tmpFactionValue) {
merchant->Say_StringID(MakeRandomInt(WONT_SELL_DEEDS1, WONT_SELL_DEEDS6));
} else if (lowestvalue == fmod.race_mod) { // race biggest
// Non-standard race (ex. illusioned to wolf)
if (GetRace() > PLAYER_RACE_COUNT) {
messageid = MakeRandomInt(1, 3); // these aren't sequential StringIDs :(
switch (messageid) {
case 1:
messageid = WONT_SELL_NONSTDRACE1;
break;
case 2:
messageid = WONT_SELL_NONSTDRACE2;
break;
case 3:
messageid = WONT_SELL_NONSTDRACE3;
break;
default: // w/e should never happen
messageid = WONT_SELL_NONSTDRACE1;
break;
}
merchant->Say_StringID(messageid);
} else { // normal player races
messageid = MakeRandomInt(1, 4);
switch (messageid) {
case 1:
messageid = WONT_SELL_RACE1;
break;
case 2:
messageid = WONT_SELL_RACE2;
break;
case 3:
messageid = WONT_SELL_RACE3;
break;
case 4:
messageid = WONT_SELL_RACE4;
break;
default: // w/e should never happen
messageid = WONT_SELL_RACE1;
break;
}
merchant->Say_StringID(messageid, itoa(GetRace()));
}
} else if (lowestvalue == fmod.class_mod) {
merchant->Say_StringID(MakeRandomInt(WONT_SELL_CLASS1, WONT_SELL_CLASS5), itoa(GetClass()));
}
return;
}
//o--------------------------------------------------------------
//| Name: SendFactionMessage
//o--------------------------------------------------------------
//| Purpose: Send faction change message to client
//o--------------------------------------------------------------
void Client::SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 totalvalue, uint8 temp)
{
char name[50];
// default to Faction# if we couldn't get the name from the ID
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;
else if (totalvalue >= MAX_FACTION)
Message_StringID(0, FACTION_BEST, name);
else if (tmpvalue > 0 && totalvalue < MAX_FACTION)
Message_StringID(0, FACTION_BETTER, name);
else if (tmpvalue < 0 && totalvalue > MIN_FACTION)
Message_StringID(0, FACTION_WORSE, name);
else if (totalvalue <= MIN_FACTION)
Message_StringID(0, FACTION_WORST, name);
return;
}
void Client::LoadAccountFlags()
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
accountflags.clear();
MakeAnyLenString(&query, "SELECT p_flag, p_value FROM account_flags WHERE p_accid = '%d'", account_id);
if(database.RunQuery(query, strlen(query), errbuf, &result))
{
while(row = mysql_fetch_row(result))
{
std::string fname(row[0]);
std::string fval(row[1]);
accountflags[fname] = fval;
}
mysql_free_result(result);
}
else
{
std::cerr << "Error in LoadAccountFlags query '" << query << "' " << errbuf << std::endl;
}
safe_delete_array(query);
}
void Client::SetAccountFlag(std::string flag, std::string val)
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MakeAnyLenString(&query, "REPLACE INTO account_flags (p_accid, p_flag, p_value) VALUES( '%d', '%s', '%s')", account_id, flag.c_str(), val.c_str());
if(!database.RunQuery(query, strlen(query), errbuf))
{
std::cerr << "Error in SetAccountFlags query '" << query << "' " << errbuf << std::endl;
}
safe_delete_array(query);
accountflags[flag] = val;
}
std::string Client::GetAccountFlag(std::string flag)
{
return(accountflags[flag]);
}
void Client::TickItemCheck()
{
int i;
if(zone->tick_items.empty()) { return; }
//Scan equip slots for items
for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++)
{
TryItemTick(i);
}
//Scan main inventory + cursor
for(i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++)
{
TryItemTick(i);
}
//Scan bags
for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++)
{
TryItemTick(i);
}
}
void Client::TryItemTick(int slot)
{
int iid = 0;
const ItemInst* inst = m_inv[slot];
if(inst == 0) { return; }
iid = inst->GetID();
if(zone->tick_items.count(iid) > 0)
{
if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot <= EmuConstants::EQUIPMENT_END) )
{
ItemInst* e_inst = (ItemInst*)inst;
parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot);
}
}
//Only look at augs in main inventory
if(slot > EmuConstants::EQUIPMENT_END) { return; }
for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x)
{
ItemInst * a_inst = inst->GetAugment(x);
if(!a_inst) { continue; }
iid = a_inst->GetID();
if(zone->tick_items.count(iid) > 0)
{
if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) )
{
ItemInst* e_inst = (ItemInst*)a_inst;
parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot);
}
}
}
}
void Client::ItemTimerCheck()
{
int i;
for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++)
{
TryItemTimer(i);
}
for(i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++)
{
TryItemTimer(i);
}
for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++)
{
TryItemTimer(i);
}
}
void Client::TryItemTimer(int slot)
{
ItemInst* inst = m_inv.GetItem(slot);
if(!inst) {
return;
}
auto item_timers = inst->GetTimers();
auto it_iter = item_timers.begin();
while(it_iter != item_timers.end()) {
if(it_iter->second.Check()) {
parse->EventItem(EVENT_TIMER, this, inst, nullptr, it_iter->first, 0);
}
++it_iter;
}
if(slot > EmuConstants::EQUIPMENT_END) {
return;
}
for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x)
{
ItemInst * a_inst = inst->GetAugment(x);
if(!a_inst) {
continue;
}
auto item_timers = a_inst->GetTimers();
auto it_iter = item_timers.begin();
while(it_iter != item_timers.end()) {
if(it_iter->second.Check()) {
parse->EventItem(EVENT_TIMER, this, a_inst, nullptr, it_iter->first, 0);
}
++it_iter;
}
}
}
void Client::RefundAA() {
int cur = 0;
bool refunded = false;
for(int x = 0; x < aaHighestID; x++) {
cur = GetAA(x);
if(cur > 0){
SendAA_Struct* curaa = zone->FindAA(x);
if(cur){
SetAA(x, 0);
for(int j = 0; j < cur; j++) {
m_pp.aapoints += curaa->cost + (curaa->cost_inc * j);
refunded = true;
}
}
else
{
m_pp.aapoints += cur;
SetAA(x, 0);
refunded = true;
}
}
}
if(refunded) {
SaveAA();
Save();
// Kick();
}
}
void Client::IncrementAA(int aa_id) {
SendAA_Struct* aa2 = zone->FindAA(aa_id);
if(aa2 == nullptr)
return;
if(GetAA(aa_id) == aa2->max_level)
return;
SetAA(aa_id, GetAA(aa_id) + 1);
SaveAA();
SendAA(aa_id);
SendAATable();
SendAAStats();
CalcBonuses();
}
void Client::SendItemScale(ItemInst *inst) {
int slot = m_inv.GetSlotByItemInst(inst);
if(slot != -1) {
inst->ScaleItem();
SendItemPacket(slot, inst, ItemPacketCharmUpdate);
CalcBonuses();
}
}
void Client::AddRespawnOption(std::string option_name, uint32 zoneid, float x, float y, float z, float heading, bool initial_selection, int8 position)
{
//If respawn window is already open, any changes would create an inconsistency with the client
if (IsHoveringForRespawn()) { return; }
if (zoneid == 0)
zoneid = zone->GetZoneID();
//Create respawn option
RespawnOption res_opt;
res_opt.name = option_name;
res_opt.zoneid = zoneid;
res_opt.x = x;
res_opt.y = y;
res_opt.z = z;
res_opt.heading = heading;
if (position == -1 || position >= respawn_options.size())
{
//No position specified, or specified beyond the end, simply append
respawn_options.push_back(res_opt);
//Make this option the initial selection for the window if desired
if (initial_selection)
initial_respawn_selection = static_cast<uint8>(respawn_options.size()) - 1;
}
else if (position == 0)
{
respawn_options.push_front(res_opt);
if (initial_selection)
initial_respawn_selection = 0;
}
else
{
//Insert new option between existing options
std::list<RespawnOption>::iterator itr;
uint8 pos = 0;
for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr)
{
if (pos++ == position)
{
respawn_options.insert(itr,res_opt);
//Make this option the initial selection for the window if desired
if (initial_selection)
initial_respawn_selection = pos;
return;
}
}
}
}
bool Client::RemoveRespawnOption(std::string option_name)
{
//If respawn window is already open, any changes would create an inconsistency with the client
if (IsHoveringForRespawn() || respawn_options.empty()) { return false; }
bool had = false;
RespawnOption* opt;
std::list<RespawnOption>::iterator itr;
for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr)
{
opt = &(*itr);
if (opt->name.compare(option_name) == 0)
{
itr = respawn_options.erase(itr);
had = true;
//could be more with the same name, so keep going...
}
}
return had;
}
bool Client::RemoveRespawnOption(uint8 position)
{
//If respawn window is already open, any changes would create an inconsistency with the client
if (IsHoveringForRespawn() || respawn_options.empty()) { return false; }
//Easy cases first...
if (position == 0)
{
respawn_options.pop_front();
return true;
}
else if (position == (respawn_options.size() - 1))
{
respawn_options.pop_back();
return true;
}
std::list<RespawnOption>::iterator itr;
uint8 pos = 0;
for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr)
{
if (pos++ == position)
{
respawn_options.erase(itr);
return true;
}
}
return false;
2013-06-25 10:45:07 -07:00
}
void Client::SetHunger(int32 in_hunger)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = in_hunger;
sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level;
m_pp.hunger_level = in_hunger;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SetThirst(int32 in_thirst)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level;
sta->water = in_thirst;
m_pp.thirst_level = in_thirst;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SetConsumption(int32 in_hunger, int32 in_thirst)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = in_hunger;
sta->water = in_thirst;
m_pp.hunger_level = in_hunger;
m_pp.thirst_level = in_thirst;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume)
{
if(!item) { return; }
uint16 cons_mod = 180;
int16 metabolism_bonus = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism;
if (metabolism_bonus)
cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000;
else
cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100;
if(type == ItemTypeFood)
{
int hchange = item->CastTime * cons_mod;
hchange = mod_food_value(item, hchange);
if(hchange < 0) { return; }
m_pp.hunger_level += hchange;
DeleteItemInInventory(slot, 1, false);
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
2014-04-20 00:26:54 -04:00
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot);
#endif
}
else
{
int tchange = item->CastTime * cons_mod;
tchange = mod_drink_value(item, tchange);
if(tchange < 0) { return; }
m_pp.thirst_level += tchange;
DeleteItemInInventory(slot, 1, false);
2013-11-16 03:40:35 +01:00
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
2014-04-20 00:26:54 -04:00
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot);
#endif
}
}
2013-12-23 17:35:58 -08:00
void Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg)
{
if(duration == 0 || msg.length() == 0) {
return;
}
EQApplicationPacket outapp(OP_Marquee, sizeof(ClientMarqueeMessage_Struct) + msg.length());
ClientMarqueeMessage_Struct *cms = (ClientMarqueeMessage_Struct*)outapp.pBuffer;
cms->type = type;
cms->unk04 = 10;
cms->priority = priority;
cms->fade_in_time = fade_in;
cms->fade_out_time = fade_out;
cms->duration = duration;
strcpy(cms->msg, msg.c_str());
QueuePacket(&outapp);
}
void Client::PlayMP3(const char* fname)
{
std::string filename = fname;
EQApplicationPacket *outapp = new EQApplicationPacket(OP_PlayMP3, filename.length() + 1);
PlayMP3_Struct* buf = (PlayMP3_Struct*)outapp->pBuffer;
strncpy(buf->filename, fname, filename.length());
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::ExpeditionSay(const char *str, int ExpID) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (!database.RunQuery(query,MakeAnyLenString(&query, "SELECT `player_name` FROM `cust_inst_players` WHERE `inst_id` = %i", ExpID),errbuf,&result)){
safe_delete_array(query);
return;
}
safe_delete_array(query);
if(result)
this->Message(14, "You say to the expedition, '%s'", str);
while((row = mysql_fetch_row(result))) {
const char* CharName = row[0];
if(strcmp(CharName, this->GetCleanName()) != 0)
worldserver.SendEmoteMessage(CharName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str);
// ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3]));
}
mysql_free_result(result);
}
void Client::ShowNumHits()
{
uint32 buffcount = GetMaxTotalSlots();
for (uint32 buffslot = 0; buffslot < buffcount; buffslot++) {
const Buffs_Struct &curbuff = buffs[buffslot];
if (curbuff.spellid != SPELL_UNKNOWN && curbuff.numhits)
Message(0, "You have %d hits left on %s", curbuff.numhits, GetSpellName(curbuff.spellid));
}
return;
}
float Client::GetQuiverHaste()
{
float quiver_haste = 0;
for (int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++) {
const ItemInst *pi = GetInv().GetItem(r);
if (!pi)
continue;
if (pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) {
float temp_wr = (pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv));
quiver_haste = std::max(temp_wr, quiver_haste);
}
}
if (quiver_haste > 0)
quiver_haste = 1.0f / (1.0f + static_cast<float>(quiver_haste) / 100.0f);
return quiver_haste;
}
void Client::SendColoredText(uint32 color, std::string message)
{
// arbitrary size limit
if (message.size() > 512) // live does send this with empty strings sometimes ...
return;
EQApplicationPacket *outapp = new EQApplicationPacket(OP_ColoredText,
sizeof(ColoredText_Struct) + message.size());
ColoredText_Struct *cts = (ColoredText_Struct *)outapp->pBuffer;
cts->color = color;
strcpy(cts->msg, message.c_str());
QueuePacket(outapp);
safe_delete(outapp);
}