mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
Character delete time via config (default 1 hour) Cleaned up some text messages to correct error int New /getemail & /setemail for account security Use of setemail is once per account only Login screen will start to highlight last played char Only if house owner is deleted will house be lost
4575 lines
123 KiB
C++
4575 lines
123 KiB
C++
#include <StdAfx.h>
|
|
|
|
#include "Client.h"
|
|
#include "ClientCommands.h"
|
|
#include "ClientEvents.h"
|
|
#include "World.h"
|
|
#include <chrono>
|
|
|
|
#include "Database.h"
|
|
#include "DatabaseIO.h"
|
|
#include "Database2.h"
|
|
|
|
#include "ChatMsgs.h"
|
|
#include "ObjectMsgs.h"
|
|
|
|
#include "WeenieObject.h"
|
|
#include "Monster.h"
|
|
#include "Player.h"
|
|
#include "ChatMsgs.h"
|
|
#include "Movement.h"
|
|
#include "EmoteManager.h"
|
|
#include "MovementManager.h"
|
|
#include "Vendor.h"
|
|
#include "AllegianceManager.h"
|
|
#include "House.h"
|
|
#include "HouseManager.h"
|
|
#include "SpellcastingManager.h"
|
|
#include "TradeManager.h"
|
|
#include "ChessManager.h"
|
|
#include <chrono>
|
|
|
|
#include "Config.h"
|
|
#include "Messages.h"
|
|
|
|
CClientEvents::CClientEvents(CClient *parent)
|
|
{
|
|
m_pClient = parent;
|
|
m_pPlayer = NULL;
|
|
}
|
|
|
|
CClientEvents::~CClientEvents()
|
|
{
|
|
if (m_pPlayer)
|
|
{
|
|
m_pPlayer->BeginLogout();
|
|
m_pPlayer->DetachClient();
|
|
m_pPlayer = NULL;
|
|
}
|
|
}
|
|
|
|
void CClientEvents::DetachPlayer()
|
|
{
|
|
m_pPlayer = NULL;
|
|
}
|
|
|
|
uint32_t CClientEvents::GetPlayerID()
|
|
{
|
|
if (!m_pPlayer)
|
|
return 0;
|
|
|
|
return m_pPlayer->GetID();
|
|
}
|
|
|
|
CPlayerWeenie *CClientEvents::GetPlayer()
|
|
{
|
|
return m_pPlayer;
|
|
}
|
|
|
|
std::string GetAgeString(int age)
|
|
{
|
|
int mo = age / 2629800; // seconds in a month
|
|
int leftover = age % 2629800;
|
|
int d = leftover / 86400; // seconds in a day
|
|
leftover %= 86400;
|
|
int h = leftover / 3600; // seconds in an hour
|
|
leftover %= 3600;
|
|
int m = leftover / 60; // seconds in a minute
|
|
leftover %= 60;
|
|
int s = leftover;
|
|
std::string out = "You have played for ";
|
|
if (mo)
|
|
out += csprintf("%dmo ", mo);
|
|
if (d)
|
|
out += csprintf("%dd ", d);
|
|
if (h)
|
|
out += csprintf("%dh ", h);
|
|
if (m)
|
|
out += csprintf("%dm ", m);
|
|
out += csprintf("%ds.", s);
|
|
return out;
|
|
}
|
|
|
|
void CClientEvents::ExitWorld()
|
|
{
|
|
auto t = chrono::system_clock::to_time_t(chrono::system_clock::now());
|
|
m_pPlayer->m_Qualities.SetInt(LOGOFF_TIMESTAMP_INT, t);
|
|
|
|
DetachPlayer();
|
|
m_pClient->ExitWorld();
|
|
}
|
|
|
|
void CClientEvents::Think()
|
|
{
|
|
if (m_pPlayer)
|
|
{
|
|
// update in-game age
|
|
int time_now = chrono::system_clock::to_time_t(chrono::system_clock::now());
|
|
|
|
if (time_now > (last_age_update + 15))
|
|
{
|
|
int age = m_pPlayer->m_Qualities.GetInt(AGE_INT, 0);
|
|
int time_diff = time_now - last_age_update;
|
|
age += time_diff;
|
|
m_pPlayer->m_Qualities.SetInt(AGE_INT, age);
|
|
m_pPlayer->NotifyIntStatUpdated(AGE_INT);
|
|
last_age_update = time_now;
|
|
}
|
|
|
|
if (m_bSendAllegianceUpdates)
|
|
{
|
|
if (m_fNextAllegianceUpdate <= g_pGlobals->Time())
|
|
{
|
|
SendAllegianceUpdate();
|
|
m_fNextAllegianceUpdate = g_pGlobals->Time() + 5.0f;
|
|
|
|
if (!m_bSentFirstAllegianceUpdate)
|
|
{
|
|
m_bSendAllegianceUpdates = FALSE;
|
|
m_bSentFirstAllegianceUpdate = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::BeginLogout()
|
|
{
|
|
if (m_pPlayer && !m_pPlayer->IsLoggingOut())
|
|
{
|
|
if (m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
m_pPlayer->BeginLogout();
|
|
}
|
|
}
|
|
|
|
void CClientEvents::ForceLogout()
|
|
{
|
|
if (m_pPlayer && !m_pPlayer->IsLoggingOut())
|
|
{
|
|
m_pPlayer->BeginLogout();
|
|
}
|
|
}
|
|
|
|
void CClientEvents::OnLogoutCompleted()
|
|
{
|
|
ExitWorld();
|
|
}
|
|
|
|
void CClientEvents::LoginError(int iError)
|
|
{
|
|
uint32_t ErrorPackage[2];
|
|
|
|
ErrorPackage[0] = 0xF659;
|
|
ErrorPackage[1] = iError;
|
|
|
|
m_pClient->SendNetMessage(ErrorPackage, sizeof(ErrorPackage), PRIVATE_MSG);
|
|
}
|
|
|
|
void CClientEvents::LoginCharacter(uint32_t char_weenie_id, const char *szAccount)
|
|
{
|
|
if (!m_pClient->HasCharacter(char_weenie_id))
|
|
{
|
|
LoginError(13); // update error codes
|
|
SERVER_WARN << szAccount << "attempting to log in with a character that doesn't belong to this account";
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer || g_pWorld->FindPlayer(char_weenie_id) || g_pDBIO->GetNumPendingSaves(char_weenie_id) > 0)
|
|
{
|
|
// LOG(Temp, Normal, "Character already logged in!\n");
|
|
LoginError(13); // update error codes
|
|
SERVER_WARN << szAccount << "Login request, but character already logged in!";
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if (_stricmp(szAccount, m_pClient->GetAccountInfo().username))
|
|
{
|
|
LoginError(15);
|
|
LOG(Client, Warning, "Bad account for login: \"%s\" \"%s\"\n", szAccount, m_pClient->GetAccountInfo().username);
|
|
return;
|
|
}
|
|
*/
|
|
|
|
m_pPlayer = new CPlayerWeenie(m_pClient, char_weenie_id, m_pClient->IncCharacterInstanceTS(char_weenie_id));
|
|
|
|
if (!m_pPlayer->Load())
|
|
{
|
|
LoginError(13); // update error codes
|
|
SERVER_WARN << szAccount << "Login request, but character failed to load!";
|
|
|
|
delete m_pPlayer;
|
|
|
|
return;
|
|
}
|
|
|
|
m_pPlayer->m_Qualities.SetFloat(HEARTBEAT_INTERVAL_FLOAT, 5.0);
|
|
m_pPlayer->m_Qualities.SetInt(NETHER_OVER_TIME_INT, 0);
|
|
m_pPlayer->m_Qualities.SetInt(DAMAGE_OVER_TIME_INT, 0);
|
|
m_pPlayer->m_Qualities.SetFloat(ATTACK_TIMESTAMP_FLOAT, 0.0);
|
|
m_pPlayer->LoadTitles();
|
|
m_pPlayer->SendTitles();
|
|
m_pPlayer->SetLoginPlayerQualities(); // overrides
|
|
m_pPlayer->RecalculateEncumbrance();
|
|
m_pPlayer->LoginCharacter();
|
|
m_pPlayer->SendFullFriendData();
|
|
m_pPlayer->LoadSquelches();
|
|
m_pPlayer->LoadCorpses();
|
|
last_age_update = chrono::system_clock::to_time_t(chrono::system_clock::now());
|
|
|
|
if (m_pPlayer->m_Qualities._skillStatsTable)
|
|
{
|
|
// fix for trained skills higher than they should be on char create.
|
|
for (uint32_t i = 0; i < m_pPlayer->m_Qualities._skillStatsTable->size(); i++)
|
|
{
|
|
Skill trained;
|
|
m_pPlayer->m_Qualities.InqSkill((STypeSkill)i, trained);
|
|
|
|
if (trained._sac == SKILL_ADVANCEMENT_CLASS::TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
// Only do this once.
|
|
if (trained._init_level > 0)
|
|
{
|
|
trained._init_level = 0;
|
|
trained._level_from_pp = trained._level_from_pp + 5;
|
|
if (trained._level_from_pp > 208)
|
|
trained._level_from_pp = 208;
|
|
|
|
m_pPlayer->m_Qualities.SetSkill((STypeSkill)i, trained);
|
|
m_pPlayer->NotifySkillStatUpdated((STypeSkill)i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// give characters created before creation timestamp was being set a timestamp and DOB from their DB date_created
|
|
if (!m_pPlayer->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0))
|
|
{
|
|
CharacterDesc_t char_info = g_pDBIO->GetCharacterInfo(m_pPlayer->GetID());
|
|
if (char_info.date_created) // check that the query got info, will be 0 if it didn't
|
|
{
|
|
time_t t = char_info.date_created;
|
|
m_pPlayer->m_Qualities.SetInt(CREATION_TIMESTAMP_INT, t);
|
|
m_pPlayer->NotifyIntStatUpdated(CREATION_TIMESTAMP_INT);
|
|
|
|
std::stringstream ss;
|
|
ss << "You were born on " << std::put_time(std::localtime(&t), "%m/%d/%y %I:%M:%S %p."); // convert time to a string of format '01/01/18 11:59:59 AM.'
|
|
m_pPlayer->m_Qualities.SetString(DATE_OF_BIRTH_STRING, ss.str());
|
|
m_pPlayer->NotifyStringStatUpdated(DATE_OF_BIRTH_STRING);
|
|
}
|
|
}
|
|
|
|
if (!m_pPlayer->m_Qualities.GetFloat(GLOBAL_XP_MOD_FLOAT, 0.0))
|
|
{
|
|
m_pPlayer->m_Qualities.SetFloat(GLOBAL_XP_MOD_FLOAT, 1.0);
|
|
m_pPlayer->NotifyFloatStatUpdated(GLOBAL_XP_MOD_FLOAT);
|
|
}
|
|
|
|
last_age_update = chrono::system_clock::to_time_t(chrono::system_clock::now());
|
|
|
|
//temporarily send a purge all enchantments packet on login to wipe stacked characters.
|
|
if (g_pConfig->SpellPurgeOnLogin())
|
|
{
|
|
if (m_pPlayer->m_Qualities._enchantment_reg)
|
|
{
|
|
PackableList<uint32_t> removed;
|
|
|
|
if (m_pPlayer->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (auto it = m_pPlayer->m_Qualities._enchantment_reg->_add_list->begin(); it != m_pPlayer->m_Qualities._enchantment_reg->_add_list->end();)
|
|
{
|
|
if (it->_duration == -1.0)
|
|
{
|
|
removed.push_back(it->_id);
|
|
it = m_pPlayer->m_Qualities._enchantment_reg->_add_list->erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_pPlayer->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (auto it = m_pPlayer->m_Qualities._enchantment_reg->_mult_list->begin(); it != m_pPlayer->m_Qualities._enchantment_reg->_mult_list->end();)
|
|
{
|
|
if (it->_duration == -1.0)
|
|
{
|
|
removed.push_back(it->_id);
|
|
it = m_pPlayer->m_Qualities._enchantment_reg->_mult_list->erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removed.size())
|
|
{
|
|
// m_Qualities._enchantment_reg->PurgeEnchantments();
|
|
|
|
BinaryWriter expireMessage;
|
|
expireMessage.Write<uint32_t>(0x2C8);
|
|
removed.Pack(&expireMessage);
|
|
|
|
m_pClient->SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// give characters created before creation timestamp was being set a timestamp and DOB from their DB date_created
|
|
if (!m_pPlayer->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0))
|
|
{
|
|
CharacterDesc_t char_info = g_pDBIO->GetCharacterInfo(m_pPlayer->GetID());
|
|
if (char_info.date_created) // check that the query got info, will be 0 if it didn't
|
|
{
|
|
time_t t = char_info.date_created;
|
|
m_pPlayer->m_Qualities.SetInt(CREATION_TIMESTAMP_INT, t);
|
|
m_pPlayer->NotifyIntStatUpdated(CREATION_TIMESTAMP_INT);
|
|
|
|
std::stringstream ss;
|
|
ss << std::put_time(std::localtime(&t), "%m/%d/%y %I:%M:%S %p."); // convert time to a string of format '01/01/18 11:59:59 AM.'
|
|
m_pPlayer->m_Qualities.SetString(DATE_OF_BIRTH_STRING, ss.str());
|
|
m_pPlayer->NotifyStringStatUpdated(DATE_OF_BIRTH_STRING);
|
|
}
|
|
}
|
|
|
|
if (m_pPlayer->m_Qualities.GetInt(HERITAGE_GROUP_INT, 0) == Lugian_HeritageGroup)
|
|
m_pPlayer->m_Qualities.SetDataID(MOTION_TABLE_DID, 0x9000216);
|
|
|
|
if (m_pPlayer->m_Qualities.GetInt(HERITAGE_GROUP_INT, 0) == Empyrean_HeritageGroup && m_pPlayer->m_Qualities.GetDID(MOTION_TABLE_DID, 0x9000001) == 0x9000001)
|
|
m_pPlayer->m_Qualities.SetDataID(MOTION_TABLE_DID, 0x9000207);
|
|
|
|
m_pPlayer->SendText("GDLEnhanced " SERVER_VERSION_NUMBER_STRING " " SERVER_VERSION_STRING, LTT_DEFAULT);
|
|
m_pPlayer->SendText("Maintained by the GDLE Development Team. Contact us at https://discord.gg/WzGX348", LTT_DEFAULT);
|
|
m_pPlayer->SendText("Not an official Asheron's Call server.", LTT_DEFAULT);
|
|
SendAllegianceMOTD();
|
|
|
|
#pragma region RatingsCleanup
|
|
if (g_pConfig->RatingPurgeOnLogin())
|
|
{
|
|
m_pPlayer->m_Qualities.SetInt(DAMAGE_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(DAMAGE_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(DAMAGE_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(DAMAGE_RESIST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(CRIT_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(CRIT_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(CRIT_DAMAGE_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(CRIT_DAMAGE_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(CRIT_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(CRIT_RESIST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(CRIT_DAMAGE_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(CRIT_DAMAGE_RESIST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(HEALING_BOOST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(HEALING_BOOST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(NETHER_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(NETHER_RESIST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(LIFE_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(LIFE_RESIST_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(PK_DAMAGE_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(PK_DAMAGE_RATING_INT);
|
|
m_pPlayer->m_Qualities.SetInt(PK_DAMAGE_RESIST_RATING_INT, 0);
|
|
m_pPlayer->NotifyIntStatUpdated(PK_DAMAGE_RESIST_RATING_INT);
|
|
|
|
// parse through wielded and set based upon GEAR_RATING
|
|
for (auto wielded : m_pPlayer->m_Wielded)
|
|
{
|
|
STypeInt rating;
|
|
int ratingVal = 0;
|
|
int gearRating = 0;
|
|
for (int i = 370; i < 380; i++)
|
|
{
|
|
if (wielded->m_Qualities.InqInt((STypeInt)i, gearRating))
|
|
{
|
|
switch (i)
|
|
{
|
|
case GEAR_DAMAGE_INT:
|
|
rating = DAMAGE_RATING_INT;
|
|
break;
|
|
case GEAR_DAMAGE_RESIST_INT:
|
|
rating = DAMAGE_RESIST_RATING_INT;
|
|
break;
|
|
case GEAR_CRIT_INT:
|
|
rating = CRIT_RATING_INT;
|
|
break;
|
|
case GEAR_CRIT_RESIST_INT:
|
|
rating = CRIT_RESIST_RATING_INT;
|
|
break;
|
|
case GEAR_CRIT_DAMAGE_INT:
|
|
rating = CRIT_DAMAGE_RATING_INT;
|
|
break;
|
|
case GEAR_CRIT_DAMAGE_RESIST_INT:
|
|
rating = CRIT_DAMAGE_RESIST_RATING_INT;
|
|
break;
|
|
case GEAR_HEALING_BOOST_INT:
|
|
rating = HEALING_BOOST_RATING_INT;
|
|
break;
|
|
case GEAR_NETHER_RESIST_INT:
|
|
rating = NETHER_RESIST_RATING_INT;
|
|
break;
|
|
case GEAR_LIFE_RESIST_INT:
|
|
rating = LIFE_RESIST_RATING_INT;
|
|
break;
|
|
case GEAR_MAX_HEALTH_INT:
|
|
rating = GEAR_MAX_HEALTH_INT;
|
|
break;
|
|
case GEAR_PK_DAMAGE_RATING_INT:
|
|
rating = PK_DAMAGE_RATING_INT;
|
|
break;
|
|
case GEAR_PK_DAMAGE_RESIST_RATING_INT:
|
|
rating = PK_DAMAGE_RESIST_RATING_INT;
|
|
break;
|
|
}
|
|
|
|
m_pPlayer->m_Qualities.InqInt(rating, ratingVal);
|
|
m_pPlayer->m_Qualities.SetInt(rating, ratingVal + gearRating);
|
|
m_pPlayer->NotifyIntStatUpdated(rating);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
if (!g_pWorld->CreateEntity(m_pPlayer))
|
|
return;
|
|
|
|
time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
|
|
|
for (auto wielded : m_pPlayer->m_Wielded)
|
|
{
|
|
// Updates shields to have SHIELD_VALUE_INT
|
|
if (wielded->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && wielded->InqIntQuality(LOCATIONS_INT, 0) == SHIELD_LOC)
|
|
{
|
|
wielded->m_Qualities.SetInt(SHIELD_VALUE_INT, wielded->InqIntQuality(ARMOR_LEVEL_INT, 0));
|
|
}
|
|
|
|
// Remove all loot items with wcid > g_pConfig->WcidForPurge()
|
|
if (g_pConfig->InventoryPurgeOnLogin())
|
|
{
|
|
if (wielded->m_Qualities.id > g_pConfig->WcidForPurge())
|
|
{
|
|
wielded->_timeToRot = Timer::cur_time;
|
|
wielded->_beganRot = true;
|
|
}
|
|
}
|
|
|
|
// Remove all expired items, or set _timeToRot if not yet expired.
|
|
int lifespan = wielded->m_Qualities.GetInt(LIFESPAN_INT, 0);
|
|
|
|
if (lifespan)
|
|
{
|
|
int creationTime = wielded->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0);
|
|
int lived = t - creationTime;
|
|
|
|
if (creationTime && lived >= lifespan)
|
|
{
|
|
wielded->_timeToRot = Timer::cur_time;
|
|
}
|
|
else
|
|
{
|
|
int lifespanRemaining = lifespan - lived;
|
|
wielded->_timeToRot = Timer::cur_time + lifespanRemaining;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (auto item : m_pPlayer->m_Items)
|
|
{
|
|
// Updates shields to have SHIELD_VALUE_INT
|
|
if (item->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && item->InqIntQuality(LOCATIONS_INT, 0) == SHIELD_LOC)
|
|
{
|
|
item->m_Qualities.SetInt(SHIELD_VALUE_INT, item->InqIntQuality(ARMOR_LEVEL_INT, 0));
|
|
}
|
|
|
|
if (item->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && item->InqIntQuality(LOCATIONS_INT, 0) == LOWER_ARM_WEAR_LOC)
|
|
{
|
|
item->m_Qualities.SetInt(LOCATIONS_INT, LOWER_ARM_ARMOR_LOC);
|
|
}
|
|
|
|
// Remove all loot items with wcid > g_pConfig->WcidForPurge()
|
|
if (g_pConfig->InventoryPurgeOnLogin())
|
|
{
|
|
if (item->m_Qualities.id > g_pConfig->WcidForPurge() && item->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0))
|
|
{
|
|
item->_timeToRot = Timer::cur_time;
|
|
item->_beganRot = true;
|
|
}
|
|
}
|
|
|
|
// Remove all expired items, or set _timeToRot if not yet expired.
|
|
int lifespan = item->m_Qualities.GetInt(LIFESPAN_INT, 0);
|
|
|
|
if (lifespan)
|
|
{
|
|
int creationTime = item->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0);
|
|
int lived = t - creationTime;
|
|
|
|
if (creationTime && lived >= lifespan)
|
|
{
|
|
item->_timeToRot = Timer::cur_time;
|
|
}
|
|
else
|
|
{
|
|
int lifespanRemaining = lifespan - lived;
|
|
item->_timeToRot = Timer::cur_time + lifespanRemaining;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto pack : m_pPlayer->m_Packs)
|
|
{
|
|
|
|
if (pack->m_Qualities.id != W_PACKCREATUREESSENCE_CLASS && pack->m_Qualities.id != W_PACKITEMESSENCE_CLASS && pack->m_Qualities.id != W_PACKLIFEESSENCE_CLASS &&
|
|
pack->m_Qualities.id != W_PACKWARESSENCE_CLASS && pack->m_Qualities.id != 43173)
|
|
{
|
|
if (pack->AsContainer())
|
|
{
|
|
|
|
for (auto item : pack->AsContainer()->m_Items)
|
|
{
|
|
// Updates shields to have SHIELD_VALUE_INT
|
|
if (item->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && item->InqIntQuality(LOCATIONS_INT, 0) == SHIELD_LOC)
|
|
{
|
|
item->m_Qualities.SetInt(SHIELD_VALUE_INT, item->InqIntQuality(ARMOR_LEVEL_INT, 0));
|
|
}
|
|
|
|
if (item->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && item->InqIntQuality(LOCATIONS_INT, 0) == LOWER_ARM_WEAR_LOC)
|
|
{
|
|
item->m_Qualities.SetInt(LOCATIONS_INT, LOWER_ARM_ARMOR_LOC);
|
|
}
|
|
|
|
// Remove all loot items with wcid > g_pConfig->WcidForPurge()
|
|
if (g_pConfig->InventoryPurgeOnLogin())
|
|
{
|
|
if (item->m_Qualities.id > g_pConfig->WcidForPurge() && item->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0))
|
|
{
|
|
item->_timeToRot = Timer::cur_time;
|
|
item->_beganRot = true;
|
|
}
|
|
}
|
|
|
|
// Remove all expired items, or set _timeToRot if not yet expired.
|
|
int lifespan = item->m_Qualities.GetInt(LIFESPAN_INT, 0);
|
|
|
|
if (lifespan)
|
|
{
|
|
int creationTime = item->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0);
|
|
int lived = t - creationTime;
|
|
|
|
if (creationTime && lived >= lifespan)
|
|
{
|
|
item->_timeToRot = Timer::cur_time;
|
|
}
|
|
else
|
|
{
|
|
int lifespanRemaining = lifespan - lived;
|
|
item->_timeToRot = Timer::cur_time + lifespanRemaining;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SendSquelchDB();
|
|
|
|
//temporarily add all enchantments back from the character's wielded items
|
|
if (g_pConfig->SpellPurgeOnLogin())
|
|
{
|
|
for (auto item : m_pPlayer->m_Wielded)
|
|
{
|
|
if (item->m_Qualities._spell_book)
|
|
{
|
|
bool bShouldCast = true;
|
|
|
|
std::string name;
|
|
if (item->m_Qualities.InqString(CRAFTSMAN_NAME_STRING, name))
|
|
{
|
|
if (!name.empty() && name != item->InqStringQuality(NAME_STRING, ""))
|
|
{
|
|
bShouldCast = false;
|
|
|
|
m_pPlayer->NotifyWeenieErrorWithString(WERROR_ACTIVATION_NOT_CRAFTSMAN, name.c_str());
|
|
}
|
|
}
|
|
|
|
int difficulty;
|
|
difficulty = 0;
|
|
if (item->m_Qualities.InqInt(ITEM_DIFFICULTY_INT, difficulty, TRUE, FALSE))
|
|
{
|
|
uint32_t skillLevel = 0;
|
|
if (!m_pPlayer->m_Qualities.InqSkill(ARCANE_LORE_SKILL, skillLevel, FALSE) || (int)skillLevel < difficulty)
|
|
{
|
|
bShouldCast = false;
|
|
|
|
m_pPlayer->NotifyWeenieError(WERROR_ACTIVATION_ARCANE_LORE_TOO_LOW);
|
|
}
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
difficulty = 0;
|
|
uint32_t skillActivationTypeDID = 0;
|
|
|
|
|
|
|
|
if (item->m_Qualities.InqInt(ITEM_SKILL_LEVEL_LIMIT_INT, difficulty, TRUE, FALSE) && item->m_Qualities.InqDataID(ITEM_SKILL_LIMIT_DID, skillActivationTypeDID))
|
|
{
|
|
STypeSkill skillActivationType = SkillTable::OldToNewSkill((STypeSkill)skillActivationTypeDID);
|
|
|
|
uint32_t skillLevel = 0;
|
|
if (!m_pPlayer->m_Qualities.InqSkill(skillActivationType, skillLevel, FALSE) || (int)skillLevel < difficulty)
|
|
{
|
|
bShouldCast = false;
|
|
|
|
m_pPlayer->NotifyWeenieErrorWithString(WERROR_ACTIVATION_SKILL_TOO_LOW, CachedSkillTable->GetSkillName(skillActivationType).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bShouldCast && item->InqIntQuality(ITEM_ALLEGIANCE_RANK_LIMIT_INT, 0) > item->InqIntQuality(ALLEGIANCE_RANK_INT, 0))
|
|
{
|
|
bShouldCast = false;
|
|
m_pPlayer->NotifyInventoryFailedEvent(item->GetID(), WERROR_ACTIVATION_RANK_TOO_LOW);
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
int heritageRequirement = item->InqIntQuality(HERITAGE_GROUP_INT, -1);
|
|
if (heritageRequirement != -1 && heritageRequirement != item->InqIntQuality(HERITAGE_GROUP_INT, 0))
|
|
{
|
|
bShouldCast = false;
|
|
std::string heritageString = item->InqStringQuality(ITEM_HERITAGE_GROUP_RESTRICTION_STRING, "of the correct heritage");
|
|
m_pPlayer->NotifyWeenieErrorWithString(WERROR_ACTIVATION_WRONG_RACE, heritageString.c_str());
|
|
}
|
|
}
|
|
|
|
int currentMana = 0;
|
|
if (bShouldCast && item->m_Qualities.InqInt(ITEM_CUR_MANA_INT, currentMana, TRUE, FALSE))
|
|
{
|
|
if (currentMana == 0)
|
|
{
|
|
bShouldCast = false;
|
|
m_pPlayer->NotifyWeenieError(WERROR_ACTIVATION_NOT_ENOUGH_MANA);
|
|
}
|
|
else
|
|
item->_nextManaUse = Timer::cur_time;
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
uint32_t serial = 0;
|
|
serial |= ((uint32_t)m_pPlayer->GetEnchantmentSerialByteForMask(item->InqIntQuality(LOCATIONS_INT, 0, TRUE)) << (uint32_t)0);
|
|
serial |= ((uint32_t)m_pPlayer->GetEnchantmentSerialByteForMask(item->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE)) << (uint32_t)8);
|
|
|
|
for (auto &spellPage : item->m_Qualities._spell_book->_spellbook)
|
|
{
|
|
item->MakeSpellcastingManager()->CastSpellEquipped(m_pPlayer->GetID(), spellPage.first, (WORD)serial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_pPlayer->DebugValidate();
|
|
|
|
// 1.32 Skill Reset Grant
|
|
std::string resetSkillQuest132 = "ResetFreeSkillQuest132";
|
|
bool resetGiven = m_pPlayer->_questTable.HasQuest(resetSkillQuest132.c_str());
|
|
int freeUsed = m_pPlayer->_questTable.InqQuestSolves("UsedFreeSkillReset");
|
|
if (!resetGiven && freeUsed == 1)
|
|
{
|
|
m_pPlayer->_questTable.RemoveQuest("UsedFreeSkillReset");
|
|
m_pPlayer->_questTable.AddQuest(resetSkillQuest132.c_str());
|
|
|
|
BinaryWriter popupMessage;
|
|
popupMessage.Write<uint32_t>(0x4);
|
|
popupMessage.WriteString("You have been granted a free skill reset.\nVisit Asheron's Castle to reset skills.");
|
|
|
|
m_pPlayer->SendNetMessage(&popupMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
}
|
|
|
|
void CClientEvents::SendSquelchDB()
|
|
{
|
|
|
|
BinaryWriter setSquelchDB;
|
|
setSquelchDB.Write<uint32_t>(0x01F4u);
|
|
setSquelchDB.Write<WORD>(0); // Number of accounts squelched - NYI
|
|
setSquelchDB.Write<WORD>(0); // Size of ints for above
|
|
|
|
std::list<CharacterSquelch_t> squelches = m_pPlayer->GetSquelches();
|
|
|
|
setSquelchDB.Write<WORD>(squelches.size());
|
|
setSquelchDB.Write<WORD>(32); // Size of ints for above
|
|
|
|
for (std::list<CharacterSquelch_t>::iterator it = squelches.begin(); it != squelches.end(); ++it)
|
|
{
|
|
setSquelchDB.Write<uint32_t>(it->squelched_id);
|
|
if (it->account_id > 0)
|
|
{
|
|
setSquelchDB.Write<uint32_t>(4);
|
|
setSquelchDB.Write<uint32_t>(SquelchMasks::AllChannels_Mask);
|
|
setSquelchDB.Write<uint32_t>(SquelchMasks::AllChannels_Mask);
|
|
setSquelchDB.Write<uint32_t>(SquelchMasks::AllChannels_Mask);
|
|
setSquelchDB.Write<uint32_t>(SquelchMasks::AllChannels_Mask);
|
|
}
|
|
setSquelchDB.WriteString(g_pWorld->GetPlayerName(it->squelched_id, true));
|
|
setSquelchDB.Write<uint32_t>(1);
|
|
}
|
|
|
|
setSquelchDB.Write<WORD>(0); // Number of global squelches - NYI
|
|
setSquelchDB.Write<WORD>(0); // Size of ints for above
|
|
setSquelchDB.Write<uint32_t>(0); // Number of globals squelched - NYI
|
|
setSquelchDB.Write<uint32_t>(0); // Number of accounts squelched - NYI
|
|
|
|
m_pClient->SendNetMessage(&setSquelchDB, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
void CClientEvents::SendText(const char *szText, int32_t lColor)
|
|
{
|
|
m_pClient->SendNetMessage(ServerText(szText, lColor), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
|
|
void CClientEvents::Attack(uint32_t target, uint32_t height, float power)
|
|
{
|
|
|
|
if (height <= 0 || height >= ATTACK_HEIGHT::NUM_ATTACK_HEIGHTS)
|
|
{
|
|
SERVER_WARN << "Bad melee attack height" << height << "sent by player" << m_pPlayer->GetID();
|
|
return;
|
|
}
|
|
|
|
if (power < 0.0f || power > 1.0f)
|
|
{
|
|
SERVER_WARN << "Bad melee attack power" << power << "sent by player" << m_pPlayer->GetID();
|
|
return;
|
|
}
|
|
|
|
m_pPlayer->TryMeleeAttack(target, (ATTACK_HEIGHT)height, power);
|
|
}
|
|
|
|
void CClientEvents::MissileAttack(uint32_t target, uint32_t height, float power)
|
|
{
|
|
if (height <= 0 || height >= ATTACK_HEIGHT::NUM_ATTACK_HEIGHTS)
|
|
{
|
|
SERVER_WARN << "Bad missile attack height" << height << "sent by player" << m_pPlayer->GetID();
|
|
return;
|
|
}
|
|
|
|
if (power < 0.0f || power > 1.0f)
|
|
{
|
|
SERVER_WARN << "Bad missile attack power" << power << "sent by player" << m_pPlayer->GetID();
|
|
return;
|
|
}
|
|
|
|
m_pPlayer->TryMissileAttack(target, (ATTACK_HEIGHT)height, power);
|
|
}
|
|
|
|
|
|
void CClientEvents::SendTell(const char* szText, const char* targetName, const uint32_t targetId)
|
|
{
|
|
CPlayerWeenie *pTarget;
|
|
|
|
if (targetId > 0) // From SendTellByGuid
|
|
{
|
|
if (!(pTarget = g_pWorld->FindPlayer(targetId)))
|
|
{
|
|
CWeenieObject *target = g_pWorld->FindObject(targetId);
|
|
|
|
if (target && target->m_Qualities._emote_table && !target->m_Qualities._emote_table->_emote_table.empty()) {
|
|
char szResponse[300];
|
|
snprintf(szResponse, 300, "You tell %s, \"%s\"", target->GetName().c_str(), szText);
|
|
m_pPlayer->SendNetMessage(ServerText(szResponse, 4), PRIVATE_MSG, FALSE, TRUE);
|
|
target->MakeEmoteManager()->ChanceExecuteEmoteSet(ReceiveTalkDirect_EmoteCategory, szText, m_pPlayer->GetID());
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(pTarget = g_pWorld->FindPlayer(targetName)))
|
|
return;
|
|
}
|
|
|
|
if (strlen(szText) > 300)
|
|
return;
|
|
|
|
if (pTarget->GetID() == m_pPlayer->GetID())
|
|
{
|
|
std::string filteredText = FilterBadChatCharacters(szText);
|
|
char szResponse[300];
|
|
snprintf(szResponse, 300, "You think, \"%s\"", szText);
|
|
m_pPlayer->SendNetMessage(ServerText(szResponse, 3), PRIVATE_MSG, FALSE, TRUE);
|
|
return;
|
|
}
|
|
|
|
if (!CheckForChatSpam())
|
|
return;
|
|
|
|
if (!pTarget->IsPlayerSquelched(m_pPlayer->GetID(), true))
|
|
{
|
|
std::string filteredText = FilterBadChatCharacters(szText);
|
|
char szResponse[300];
|
|
snprintf(szResponse, 300, "You tell %s, \"%s\"", pTarget->GetName().c_str(), szText);
|
|
m_pPlayer->SendNetMessage(ServerText(szResponse, 4), PRIVATE_MSG, FALSE, TRUE);
|
|
pTarget->SendNetMessage(DirectChat(szText, m_pPlayer->GetName().c_str(), m_pPlayer->GetID(), pTarget->GetID(), 3), PRIVATE_MSG, TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
void CClientEvents::ClientText(const char *szText)
|
|
{
|
|
if (strlen(szText) > 500)
|
|
return;
|
|
|
|
//should really check for invalid characters and such ;]
|
|
|
|
while (szText[0] == ' ') //Skip leading spaces.
|
|
szText++;
|
|
if (szText[0] == '\0') //Make sure the text isn't blank
|
|
return;
|
|
|
|
std::string filteredText = m_pClient->GetAccessLevel() >= SENTINEL_ACCESS ? szText : FilterBadChatCharacters(szText);
|
|
szText = filteredText.c_str(); // hacky
|
|
|
|
if (szText[0] == '@' || szText[0] == '/')
|
|
{
|
|
CommandBase::Execute((char *)(++szText), m_pClient);
|
|
}
|
|
else
|
|
{
|
|
if (CheckForChatSpam())
|
|
{
|
|
m_pPlayer->SpeakLocal(szText);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::EmoteText(const char* szText)
|
|
{
|
|
if (strlen(szText) > 300)
|
|
return;
|
|
|
|
//TODO: Check for invalid characters and such ;)
|
|
|
|
while (szText[0] == ' ') //Skip leading spaces.
|
|
szText++;
|
|
if (szText[0] == '\0') //Make sure the text isn't blank
|
|
return;
|
|
|
|
m_pPlayer->EmoteLocal(szText);
|
|
}
|
|
|
|
void CClientEvents::ActionText(const char *text)
|
|
{
|
|
if (strlen(text) > 300)
|
|
return;
|
|
|
|
while (text[0] == ' ') //Skip leading spaces.
|
|
text++;
|
|
if (text[0] == '\0') //Make sure the text isn't blank
|
|
return;
|
|
|
|
m_pPlayer->ActionLocal(text);
|
|
}
|
|
|
|
void CClientEvents::ChannelText(uint32_t channel_id, const char *text)
|
|
{
|
|
if (strlen(text) > 300)
|
|
return;
|
|
|
|
// TODO: check for invalid characters and such
|
|
while (text[0] == ' ')
|
|
text++;
|
|
if (text[0] == '\0')
|
|
return;
|
|
|
|
switch (channel_id)
|
|
{
|
|
case Fellow_ChannelID:
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
g_pFellowshipManager->Chat(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), text);
|
|
CHAT_LOG << m_pPlayer->GetName().c_str() << "says (fellowship)," << text;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Patron_ChannelID:
|
|
g_pAllegianceManager->ChatPatron(m_pPlayer->GetID(), text);
|
|
CHAT_LOG << m_pPlayer->GetName().c_str() << "says (patron)," << text;
|
|
break;
|
|
|
|
case Vassals_ChannelID:
|
|
g_pAllegianceManager->ChatVassals(m_pPlayer->GetID(), text);
|
|
CHAT_LOG << m_pPlayer->GetName().c_str() << "says (vassals)," << text;
|
|
break;
|
|
|
|
case Covassals_ChannelID:
|
|
g_pAllegianceManager->ChatCovassals(m_pPlayer->GetID(), text);
|
|
CHAT_LOG << m_pPlayer->GetName().c_str() << "says (covassals)," << text;
|
|
break;
|
|
|
|
case Monarch_ChannelID:
|
|
g_pAllegianceManager->ChatMonarch(m_pPlayer->GetID(), text);
|
|
CHAT_LOG << m_pPlayer->GetName().c_str() << "says (monarch)," << text;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CClientEvents::RequestHealthUpdate(uint32_t dwGUID)
|
|
{
|
|
CWeenieObject *pEntity = g_pWorld->FindWithinPVS(m_pPlayer, dwGUID);
|
|
|
|
if (pEntity)
|
|
{
|
|
if (pEntity->IsCreature())
|
|
{
|
|
m_pPlayer->SetLastHealthRequest(pEntity->GetID());
|
|
m_pClient->SendNetMessage(HealthUpdate((CMonsterWeenie *)pEntity), PRIVATE_MSG, TRUE, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::ChangeCombatStance(COMBAT_MODE mode)
|
|
{
|
|
m_pPlayer->ChangeCombatMode(mode, true);
|
|
// ActionComplete();
|
|
}
|
|
|
|
void CClientEvents::ExitPortal()
|
|
{
|
|
m_pPlayer->ExitPortal();
|
|
}
|
|
|
|
void CClientEvents::Ping()
|
|
{
|
|
// Pong!
|
|
uint32_t Pong = 0x1EA;
|
|
m_pClient->SendNetMessage(&Pong, sizeof(Pong), PRIVATE_MSG, TRUE);
|
|
}
|
|
|
|
void CClientEvents::UseItemEx(uint32_t dwSourceID, uint32_t dwDestID)
|
|
{
|
|
CWeenieObject *pSource = g_pWorld->FindWithinPVS(m_pPlayer, dwSourceID);
|
|
CWeenieObject *pDest = g_pWorld->FindWithinPVS(m_pPlayer, dwDestID);
|
|
|
|
if (pSource && pSource->AsCaster())
|
|
m_pPlayer->ExecuteUseEvent(new CWandSpellUseEvent(dwSourceID, pDest ? dwDestID : dwSourceID));
|
|
else if (pSource && pDest)
|
|
pSource->UseWith(m_pPlayer, pDest);
|
|
else
|
|
ActionComplete();
|
|
}
|
|
|
|
void CClientEvents::UseObject(uint32_t dwEID)
|
|
{
|
|
if (m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
ActionComplete(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
CWeenieObject *pTarget = g_pWorld->FindWithinPVS(m_pPlayer, dwEID);
|
|
|
|
if (pTarget)
|
|
{
|
|
if (pTarget->AsContainer() && pTarget->AsContainer()->_openedById == m_pPlayer->GetID())
|
|
{
|
|
//we're closing a chest
|
|
pTarget->AsContainer()->OnContainerClosed(m_pPlayer);
|
|
ActionComplete();
|
|
return;
|
|
}
|
|
|
|
int error = pTarget->UseChecked(m_pPlayer);
|
|
|
|
if (error != WERROR_NONE)
|
|
ActionComplete(error);
|
|
}
|
|
else
|
|
{
|
|
ActionComplete(WERROR_OBJECT_GONE);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::ActionComplete(int error)
|
|
{
|
|
if (!m_pPlayer)
|
|
return;
|
|
|
|
m_pPlayer->NotifyUseDone(error);
|
|
}
|
|
|
|
void CClientEvents::Identify(uint32_t target_id)
|
|
{
|
|
//if (_next_allowed_identify > Timer::cur_time)
|
|
//{
|
|
// // do not allow to ID too fast
|
|
// return;
|
|
//}
|
|
|
|
/*
|
|
CWeenieObject *pTarget = g_pWorld->FindWithinPVS(m_pPlayer, target_id);
|
|
|
|
if (!pTarget)
|
|
{
|
|
// used to check for vendor items, temporary, should be changed
|
|
pTarget = g_pWorld->FindObject(target_id);
|
|
}
|
|
*/
|
|
|
|
CWeenieObject *pTarget = g_pWorld->FindObject(target_id);
|
|
|
|
#ifndef PUBLIC_BUILD
|
|
if (pTarget)
|
|
#else
|
|
int vis = 0;
|
|
if (pTarget && !pTarget->m_Qualities.InqBool(VISIBILITY_BOOL, vis))
|
|
#endif
|
|
{
|
|
pTarget->TryIdentify(m_pPlayer);
|
|
m_pPlayer->SetLastAssessed(pTarget->GetID());
|
|
}
|
|
|
|
//_next_allowed_identify = Timer::cur_time + 0.5;
|
|
}
|
|
|
|
void CClientEvents::SpendAttributeXP(STypeAttribute key, uint32_t exp)
|
|
{
|
|
// TODO use attribute map
|
|
if (key < 1 || key > 6)
|
|
return;
|
|
|
|
// TODO verify they are trying to spend the correct amount of XP
|
|
|
|
int64_t unassignedExp = 0;
|
|
m_pPlayer->m_Qualities.InqInt64(AVAILABLE_EXPERIENCE_INT64, unassignedExp);
|
|
if ((uint64_t)unassignedExp < (uint64_t)exp)
|
|
{
|
|
// Not enough experience
|
|
return;
|
|
}
|
|
|
|
Attribute attr;
|
|
if (!m_pPlayer->m_Qualities.InqAttribute(key, attr))
|
|
{
|
|
// Doesn't have the attribute
|
|
return;
|
|
}
|
|
|
|
const uint32_t amountNeededForMaxXp = attr.GetXpNeededForMaxXp();
|
|
|
|
exp = min(exp, amountNeededForMaxXp);
|
|
|
|
if (exp > 0)
|
|
{
|
|
m_pPlayer->GiveAttributeXP(key, exp);
|
|
m_pPlayer->m_Qualities.SetInt64(AVAILABLE_EXPERIENCE_INT64, (uint64_t)unassignedExp - (uint64_t)exp);
|
|
|
|
m_pPlayer->NotifyAttributeStatUpdated(key);
|
|
m_pPlayer->NotifyInt64StatUpdated(AVAILABLE_EXPERIENCE_INT64);
|
|
}
|
|
|
|
}
|
|
|
|
void CClientEvents::SpendAttribute2ndXP(STypeAttribute2nd key, uint32_t exp)
|
|
{
|
|
// TODO use vital map
|
|
if (key != 1 && key != 3 && key != 5)
|
|
return;
|
|
|
|
// TODO verify they are trying to spend the correct amount of XP
|
|
|
|
__int64 unassignedExp = 0;
|
|
m_pPlayer->m_Qualities.InqInt64(AVAILABLE_EXPERIENCE_INT64, unassignedExp);
|
|
if ((uint64_t)unassignedExp < (uint64_t)exp)
|
|
{
|
|
// Not enough experience
|
|
return;
|
|
}
|
|
|
|
SecondaryAttribute attr;
|
|
if (!m_pPlayer->m_Qualities.InqAttribute2nd(key, attr))
|
|
{
|
|
// Doesn't have the secondary attribute
|
|
return;
|
|
}
|
|
|
|
const uint32_t amountNeededForMaxXp = attr.GetXpNeededForMaxXp();
|
|
|
|
// If the exp is more than is needed to reach max, it is limited to the amount needed to reach max
|
|
// This is done as the client may send more than the amount needed if it is desynced
|
|
exp = min(exp, amountNeededForMaxXp);
|
|
|
|
if (exp > 0)
|
|
{
|
|
m_pPlayer->GiveAttribute2ndXP(key, exp);
|
|
m_pPlayer->m_Qualities.SetInt64(AVAILABLE_EXPERIENCE_INT64, (uint64_t)unassignedExp - (uint64_t)exp);
|
|
m_pPlayer->NotifyInt64StatUpdated(AVAILABLE_EXPERIENCE_INT64);
|
|
}
|
|
|
|
fellowship_ptr_t fs = m_pPlayer->GetFellowship();
|
|
if (fs)
|
|
{
|
|
fs->StatsUpdate(m_pPlayer->id);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::SpendSkillXP(STypeSkill key, uint32_t exp)
|
|
{
|
|
// TODO verify they are trying to spend the correct amount of XP
|
|
|
|
int64_t unassignedExp = 0;
|
|
m_pPlayer->m_Qualities.InqInt64(AVAILABLE_EXPERIENCE_INT64, unassignedExp);
|
|
if ((uint64_t)unassignedExp < (uint64_t)exp)
|
|
{
|
|
// Not enough experience
|
|
return;
|
|
}
|
|
|
|
Skill skill;
|
|
if (!m_pPlayer->m_Qualities.InqSkill(key, skill) || skill._sac < TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
// Skill doesn't exist or isn't trained
|
|
return;
|
|
}
|
|
|
|
const uint32_t amountNeededForMaxXp = skill.GetXpNeededForMaxXp();
|
|
|
|
// If the exp is more than is needed to reach max, it is limited to the amount needed to reach max
|
|
// This is done as the client may send more than the amount needed if it is desynced
|
|
exp = min(exp, amountNeededForMaxXp);
|
|
|
|
if (exp > 0)
|
|
{
|
|
// Only give the skill exp if it's not maxed and only take exp if GiveSkillXP does not return 0
|
|
if (m_pPlayer->GiveSkillXP(key, exp))
|
|
{
|
|
m_pPlayer->m_Qualities.SetInt64(AVAILABLE_EXPERIENCE_INT64, (uint64_t)unassignedExp - (uint64_t)exp);
|
|
m_pPlayer->NotifyInt64StatUpdated(AVAILABLE_EXPERIENCE_INT64);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CClientEvents::SpendSkillCredits(STypeSkill key, uint32_t credits)
|
|
{
|
|
// TODO verify they are trying to spend the correct amount of XP
|
|
|
|
uint32_t unassignedCredits = 0;
|
|
m_pPlayer->m_Qualities.InqInt(AVAILABLE_SKILL_CREDITS_INT, *(int *)&unassignedCredits);
|
|
if (unassignedCredits < credits)
|
|
{
|
|
// Not enough experience
|
|
return;
|
|
}
|
|
|
|
Skill skill;
|
|
if (!m_pPlayer->m_Qualities.InqSkill(key, skill) || skill._sac >= TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
// Skill doesn't exist or already trained
|
|
return;
|
|
}
|
|
|
|
uint32_t costToRaise = m_pPlayer->GetCostToRaiseSkill(key);
|
|
|
|
if (m_pPlayer->GetCostToRaiseSkill(key) != credits)
|
|
{
|
|
SERVER_WARN << m_pPlayer->GetName() << "- Credit cost to raise skill does not match what player is trying to spend.";
|
|
return;
|
|
}
|
|
|
|
m_pPlayer->GiveSkillAdvancementClass(key, TRAINED_SKILL_ADVANCEMENT_CLASS);
|
|
m_pPlayer->m_Qualities._skillStatsTable->operator[](key)._level_from_pp = 5;
|
|
m_pPlayer->m_Qualities._skillStatsTable->operator[](key)._pp = 526;
|
|
m_pPlayer->NotifySkillStatUpdated(key);
|
|
|
|
m_pPlayer->m_Qualities.SetInt(AVAILABLE_SKILL_CREDITS_INT, unassignedCredits - costToRaise);
|
|
m_pPlayer->NotifyIntStatUpdated(AVAILABLE_SKILL_CREDITS_INT);
|
|
}
|
|
|
|
void CClientEvents::LifestoneRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
Position lifestone;
|
|
if (m_pPlayer->m_Qualities.InqPosition(SANCTUARY_POSITION, lifestone) && lifestone.objcell_id)
|
|
{
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->ExecuteUseEvent(new CLifestoneRecallUseEvent());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pClient->SendNetMessage(ServerText("You are not bound to a Lifestone!", 7), PRIVATE_MSG);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::MarketplaceRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->ExecuteUseEvent(new CMarketplaceRecallUseEvent());
|
|
}
|
|
}
|
|
|
|
void CClientEvents::PKArenaRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->ExecuteUseEvent(new CPKArenaUseEvent());
|
|
}
|
|
}
|
|
|
|
void CClientEvents::PKLArenaRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->ExecuteUseEvent(new CPKLArenaUseEvent());
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryInscribeItem(uint32_t object_id, const std::string &text)
|
|
{
|
|
CWeenieObject *weenie = m_pPlayer->FindContainedItem(object_id);
|
|
|
|
if (!weenie)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!weenie->IsInscribable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
weenie->m_Qualities.SetString(INSCRIPTION_STRING, text.length() <= 800 ? text : text.substr(0, 800));
|
|
weenie->m_Qualities.SetString(SCRIBE_NAME_STRING, m_pPlayer->GetName());
|
|
}
|
|
|
|
void CClientEvents::TryBuyItems(uint32_t vendor_id, std::list<class ItemProfile *> &items)
|
|
{
|
|
CWeenieObject *weenie = g_pWorld->FindWithinPVS(m_pPlayer, vendor_id);
|
|
|
|
if (!weenie)
|
|
{
|
|
ActionComplete(WERROR_NO_OBJECT);
|
|
return;
|
|
}
|
|
|
|
int error = WERROR_NO_OBJECT;
|
|
if (weenie->IsVendor())
|
|
{
|
|
error = ((CVendor *)weenie)->TrySellItemsToPlayer(m_pPlayer, items);
|
|
((CVendor *)weenie)->SendVendorInventory(m_pPlayer);
|
|
}
|
|
|
|
ActionComplete(error);
|
|
}
|
|
|
|
void CClientEvents::TrySellItems(uint32_t vendor_id, std::list<class ItemProfile *> &items)
|
|
{
|
|
CWeenieObject *weenie = g_pWorld->FindWithinPVS(m_pPlayer, vendor_id);
|
|
|
|
if (!weenie)
|
|
{
|
|
ActionComplete(WERROR_NO_OBJECT);
|
|
return;
|
|
}
|
|
|
|
int error = WERROR_NO_OBJECT;
|
|
if (weenie->IsVendor())
|
|
{
|
|
error = ((CVendor *)weenie)->TryBuyItemsFromPlayer(m_pPlayer, items);
|
|
((CVendor *)weenie)->SendVendorInventory(m_pPlayer);
|
|
}
|
|
|
|
ActionComplete(error);
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipCreate(const std::string name, int shareXP)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
return;
|
|
|
|
int error = g_pFellowshipManager->Create(name, m_pPlayer->GetID(), shareXP);
|
|
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipQuit(int disband)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
// ensure we have a copy for this since it could be the last member
|
|
fellowship_ptr_t fellowship = m_pPlayer->GetFellowship();
|
|
|
|
int error = 0;
|
|
if (disband)
|
|
error = g_pFellowshipManager->Disband(fellowship, m_pPlayer->GetID());
|
|
else
|
|
error = g_pFellowshipManager->Quit(fellowship, m_pPlayer->GetID());
|
|
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipDismiss(uint32_t dismissed)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
int error = g_pFellowshipManager->Dismiss(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), dismissed);
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipRecruit(uint32_t target)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
int error = g_pFellowshipManager->Recruit(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), target);
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipUpdate(int on)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
int error = g_pFellowshipManager->RequestUpdates(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), on);
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipAssignNewLeader(uint32_t target)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
int error = g_pFellowshipManager->AssignNewLeader(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), target);
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TryFellowshipChangeOpenness(int open)
|
|
{
|
|
if (m_pPlayer->HasFellowship())
|
|
{
|
|
int error = g_pFellowshipManager->ChangeOpen(m_pPlayer->GetFellowship(), m_pPlayer->GetID(), open);
|
|
if (error)
|
|
m_pPlayer->NotifyWeenieError(error);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::SendAllegianceUpdate()
|
|
{
|
|
if (!m_pPlayer)
|
|
return;
|
|
|
|
g_pAllegianceManager->SendAllegianceProfile(m_pPlayer);
|
|
}
|
|
|
|
void CClientEvents::SendAllegianceMOTD()
|
|
{
|
|
g_pAllegianceManager->LoginMOTD(m_pPlayer);
|
|
}
|
|
|
|
void CClientEvents::SetRequestAllegianceUpdate(bool on)
|
|
{
|
|
m_bSendAllegianceUpdates = on;
|
|
}
|
|
|
|
void CClientEvents::TryBreakAllegiance(uint32_t target)
|
|
{
|
|
g_pAllegianceManager->TryBreakAllegiance(m_pPlayer, target);
|
|
}
|
|
|
|
void CClientEvents::TrySwearAllegiance(uint32_t target)
|
|
{
|
|
CPlayerWeenie *targetWeenie = g_pWorld->FindPlayer(target);
|
|
if (!targetWeenie)
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_NO_OBJECT);
|
|
return;
|
|
}
|
|
|
|
g_pAllegianceManager->TrySwearAllegiance(m_pPlayer, targetWeenie);
|
|
}
|
|
|
|
void CClientEvents::AllegianceInfoRequest(const std::string &target)
|
|
{
|
|
AllegianceTreeNode *myNode = g_pAllegianceManager->GetTreeNode(m_pPlayer->GetID());
|
|
if (!myNode)
|
|
{
|
|
m_pPlayer->SendText("You are not in an allegiance.", LTT_DEFAULT);
|
|
return;
|
|
}
|
|
|
|
AllegianceTreeNode *myMonarchNode = g_pAllegianceManager->GetTreeNode(myNode->_monarchID);
|
|
if (!myMonarchNode)
|
|
{
|
|
m_pPlayer->SendText("There was an error processing your request.", LTT_DEFAULT);
|
|
return;
|
|
}
|
|
|
|
AllegianceTreeNode *myTargetNode = myMonarchNode->FindCharByNameRecursivelySlow(target);
|
|
if (!myTargetNode)
|
|
{
|
|
m_pPlayer->SendText("Could not find allegiance member.", LTT_DEFAULT);
|
|
return;
|
|
}
|
|
|
|
unsigned int rank = 0;
|
|
if (AllegianceProfile *profile = g_pAllegianceManager->CreateAllegianceProfile(myTargetNode->_charID, &rank))
|
|
{
|
|
BinaryWriter allegianceUpdate;
|
|
allegianceUpdate.Write<uint32_t>(0x27C);
|
|
allegianceUpdate.Write<uint32_t>(myTargetNode->_charID);
|
|
profile->Pack(&allegianceUpdate);
|
|
m_pPlayer->SendNetMessage(&allegianceUpdate, PRIVATE_MSG, TRUE, FALSE);
|
|
|
|
delete profile;
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText("Error retrieving allegiance member information.", LTT_DEFAULT);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::TrySetAllegianceMOTD(const std::string &text)
|
|
{
|
|
AllegianceTreeNode *self = g_pAllegianceManager->GetTreeNode(m_pPlayer->GetID());
|
|
if (!self)
|
|
return;
|
|
|
|
if (self->_charID != self->_monarchID)
|
|
{
|
|
m_pPlayer->SendText("Only the monarch can set the Message of the Day.", LTT_DEFAULT);
|
|
return;
|
|
}
|
|
|
|
AllegianceInfo *info = g_pAllegianceManager->GetInfo(self->_monarchID);
|
|
if (!info)
|
|
return;
|
|
|
|
info->_info.m_motd = text;
|
|
info->_info.m_motdSetBy = m_pPlayer->GetName();
|
|
m_pPlayer->SendText(csprintf("MOTD changed to: \"%s\" -- %s", info->_info.m_motd.c_str(), info->_info.m_motdSetBy.c_str()), LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::AllegianceHometownRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
AllegianceTreeNode *allegianceNode = g_pAllegianceManager->GetTreeNode(m_pPlayer->GetID());
|
|
|
|
if (!allegianceNode)
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_ALLEGIANCE_NONEXISTENT);
|
|
return;
|
|
}
|
|
|
|
AllegianceInfo *allegianceInfo = g_pAllegianceManager->GetInfo(allegianceNode->_monarchID);
|
|
|
|
if (allegianceInfo && allegianceInfo->_info.m_BindPoint.objcell_id)
|
|
{
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->ExecuteUseEvent(new CAllegianceHometownRecallUseEvent());
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->NotifyWeenieError(WERROR_ALLEGIANCE_HOMETOWN_NOT_SET);
|
|
}
|
|
|
|
void CClientEvents::HouseBuy(uint32_t slumlord, const PackableList<uint32_t> &items)
|
|
{
|
|
if (CWeenieObject *slumlordObj = g_pWorld->FindObject(slumlord))
|
|
{
|
|
if (m_pPlayer->DistanceTo(slumlordObj, true) > 10.0)
|
|
return;
|
|
|
|
if (CSlumLordWeenie *slumlord = slumlordObj->AsSlumLord())
|
|
{
|
|
slumlord->BuyHouse(m_pPlayer, items);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::HouseRent(uint32_t slumlord, const PackableList<uint32_t> &items)
|
|
{
|
|
if (CWeenieObject *slumlordObj = g_pWorld->FindObject(slumlord))
|
|
{
|
|
if (m_pPlayer->DistanceTo(slumlordObj, true) > 10.0)
|
|
return;
|
|
|
|
if (CSlumLordWeenie *slumlord = slumlordObj->AsSlumLord())
|
|
{
|
|
slumlord->RentHouse(m_pPlayer, items);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::HouseAbandon()
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData && houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
houseData->AbandonHouse();
|
|
m_pPlayer->SendText("You've abandoned your house.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("That house does not belong to you.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pClient->SendNetMessage(ServerText("You do not have a house!", 7), PRIVATE_MSG);
|
|
}
|
|
|
|
void CClientEvents::HouseRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
uint32_t houseId = m_pPlayer->GetAccountHouseId();
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (m_pPlayer->GetClient() && houseData->_ownerAccount == m_pPlayer->GetClient()->GetAccountInfo().id)
|
|
{
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
m_pPlayer->ExecuteUseEvent(new CHouseRecallUseEvent());
|
|
}
|
|
else
|
|
m_pPlayer->SendText("That house does not belong to you.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pClient->SendNetMessage(ServerText("You do not have a house!", 7), PRIVATE_MSG);
|
|
}
|
|
|
|
void CClientEvents::HouseMansionRecall()
|
|
{
|
|
if (m_pPlayer->CheckPKActivity())
|
|
{
|
|
m_pPlayer->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->InqBoolQuality(RECALLS_DISABLED_BOOL, 0))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PORTAL_RECALLS_DISABLED);
|
|
return;
|
|
}
|
|
|
|
AllegianceTreeNode *allegianceNode = g_pAllegianceManager->GetTreeNode(m_pPlayer->GetID());
|
|
|
|
if (!allegianceNode)
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_ALLEGIANCE_NONEXISTENT);
|
|
return;
|
|
}
|
|
|
|
uint32_t allegianceHouseId;
|
|
|
|
CWeenieObject *monarch = g_pWorld->FindObject(allegianceNode->_monarchID);
|
|
if (!monarch)
|
|
{
|
|
monarch = CWeenieObject::Load(allegianceNode->_monarchID);
|
|
if (!monarch)
|
|
return;
|
|
allegianceHouseId = monarch->InqDIDQuality(HOUSEID_DID, 0);
|
|
delete monarch;
|
|
}
|
|
else
|
|
allegianceHouseId = monarch->InqDIDQuality(HOUSEID_DID, 0);
|
|
|
|
if (allegianceHouseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(allegianceHouseId);
|
|
if (houseData && houseData->_ownerId == allegianceNode->_monarchID && (houseData->_houseType == Villa_HouseType || houseData->_houseType == Mansion_HouseType)) //2 = villa, 3 = mansion
|
|
{
|
|
if (!m_pPlayer->IsBusyOrInAction())
|
|
m_pPlayer->ExecuteUseEvent(new CMansionRecallUseEvent());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (allegianceNode->_patronID)
|
|
m_pClient->SendNetMessage(ServerText("Your monarch does not own a mansion or villa!", 7), PRIVATE_MSG);
|
|
else
|
|
m_pClient->SendNetMessage(ServerText("You do not own a mansion or villa!", 7), PRIVATE_MSG);
|
|
}
|
|
|
|
void CClientEvents::HouseRequestData()
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
g_pHouseManager->SendHouseData(m_pPlayer, houseId);
|
|
else
|
|
{
|
|
//if we can't get the data send the "no house" packet
|
|
BinaryWriter noHouseData;
|
|
noHouseData.Write<uint32_t>(0x0226);
|
|
noHouseData.Write<uint32_t>(0);
|
|
m_pPlayer->SendNetMessage(&noHouseData, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
void CClientEvents::HouseToggleHooks(bool newSetting)
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
houseData->SetHookVisibility(newSetting);
|
|
if (newSetting)
|
|
m_pPlayer->SendText("Your dwelling's hooks are now visible.", LTT_DEFAULT); //todo: made up message.
|
|
else
|
|
m_pPlayer->SendText("Your dwelling's hooks are now hidden.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseRequestAccessList()
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
m_pPlayer->SendText("Access:", LTT_DEFAULT);
|
|
m_pPlayer->SendText(csprintf(" Public: %s", houseData->_everyoneAccess ? "Allow" : "Deny"), LTT_DEFAULT);
|
|
m_pPlayer->SendText(csprintf(" Allegiance: %s", houseData->_allegianceAccess ? "Allow" : "Deny"), LTT_DEFAULT);
|
|
|
|
m_pPlayer->SendText("Storage:", LTT_DEFAULT);
|
|
m_pPlayer->SendText(csprintf(" Public: %s", houseData->_everyoneStorageAccess ? "Allow" : "Deny"), LTT_DEFAULT);
|
|
m_pPlayer->SendText(csprintf(" Allegiance: %s", houseData->_allegianceStorageAccess ? "Allow" : "Deny"), LTT_DEFAULT);
|
|
|
|
if (houseData->_accessList.empty())
|
|
m_pPlayer->SendText("Your dwelling's acess list is empty.", LTT_DEFAULT);
|
|
else
|
|
{
|
|
m_pPlayer->SendText("Access List:", LTT_DEFAULT);
|
|
std::list<uint32_t>::iterator i = houseData->_accessList.begin();
|
|
while (i != houseData->_accessList.end())
|
|
{
|
|
std::string name = g_pWorld->GetPlayerName(*(i), true);
|
|
if (!name.empty())
|
|
{
|
|
m_pPlayer->SendText(csprintf(" %s", name.c_str()), LTT_DEFAULT);
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
i = houseData->_accessList.erase(i); //no longer exists.
|
|
houseData->Save();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (houseData->_storageAccessList.empty())
|
|
m_pPlayer->SendText("Your dwelling's storage access list is empty.", LTT_DEFAULT);
|
|
else
|
|
{
|
|
m_pPlayer->SendText("Storage Access list:", LTT_DEFAULT);
|
|
std::list<uint32_t>::iterator i = houseData->_storageAccessList.begin();
|
|
while (i != houseData->_storageAccessList.end())
|
|
{
|
|
std::string name = g_pWorld->GetPlayerName(*(i), true);
|
|
if (!name.empty())
|
|
{
|
|
m_pPlayer->SendText(csprintf(" %s", name.c_str()), LTT_DEFAULT);
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
i = houseData->_storageAccessList.erase(i); //no longer exists.
|
|
houseData->Save();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseAddPersonToAccessList(std::string name)
|
|
{
|
|
uint32_t targetId = g_pWorld->GetPlayerId(name.c_str(), true);
|
|
if (!target)
|
|
{
|
|
m_pPlayer->SendText("Can't find a player by that name.", LTT_DEFAULT); //todo: made up message.
|
|
return;
|
|
}
|
|
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (houseData->_accessList.size() > 128)
|
|
{
|
|
m_pPlayer->SendText("The access list is full", LTT_DEFAULT); //todo: made up message.
|
|
return;
|
|
}
|
|
if (std::find(houseData->_accessList.begin(), houseData->_accessList.end(), targetId) != houseData->_accessList.end())
|
|
{
|
|
m_pPlayer->SendText(csprintf("%s is already in the access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText(csprintf("You add %s to your dwelling's access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
houseData->_accessList.push_back(targetId);
|
|
houseData->Save();
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseRemovePersonFromAccessList(std::string name)
|
|
{
|
|
uint32_t targetId = g_pWorld->GetPlayerId(name.c_str(), true);
|
|
if (!target)
|
|
{
|
|
m_pPlayer->SendText("Can't find a player by that name.", LTT_DEFAULT); //todo: made up message.
|
|
return;
|
|
}
|
|
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
auto iter = std::find(houseData->_accessList.begin(), houseData->_accessList.end(), targetId);
|
|
if (iter != houseData->_accessList.end())
|
|
{
|
|
m_pPlayer->SendText(csprintf("You remove %s from your dwelling's access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
houseData->_accessList.erase(iter);
|
|
houseData->Save();
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText(csprintf("%s is not in the access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseToggleOpenAccess(bool newSetting)
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (houseData->_everyoneAccess != newSetting)
|
|
{
|
|
houseData->_everyoneAccess = newSetting;
|
|
if (newSetting)
|
|
m_pPlayer->SendText("Your dwelling is now open to the public.", LTT_DEFAULT); //todo: made up message.
|
|
else
|
|
m_pPlayer->SendText("Your dwelling is now private.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
{
|
|
if (newSetting)
|
|
m_pPlayer->SendText("Your dwelling is already open to the public.", LTT_DEFAULT); //todo: made up message.
|
|
else
|
|
m_pPlayer->SendText("Your dwelling is already private.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseToggleOpenStorageAccess()
|
|
{
|
|
//not sure how this worked? Is this a toggle? If not which command was used to disable it?
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (!houseData->_everyoneStorageAccess)
|
|
{
|
|
m_pPlayer->SendText("Your dwelling's storage is now open to the public.", LTT_DEFAULT); //todo: made up message.
|
|
houseData->_everyoneStorageAccess = true;
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText("Your dwelling's storage is now private.", LTT_DEFAULT); //todo: made up message.
|
|
houseData->_everyoneStorageAccess = false;
|
|
}
|
|
houseData->Save();
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseAddOrRemovePersonToStorageList(std::string name, bool isAdd)
|
|
{
|
|
uint32_t targetId = g_pWorld->GetPlayerId(name.c_str(), true);
|
|
if (!target)
|
|
{
|
|
m_pPlayer->SendText("Can't find a player by that name.", LTT_DEFAULT); //todo: made up message.
|
|
return;
|
|
}
|
|
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (isAdd)
|
|
{
|
|
if (houseData->_accessList.size() > 128)
|
|
{
|
|
m_pPlayer->SendText("The storage access list is full", LTT_DEFAULT); //todo: made up message.
|
|
return;
|
|
}
|
|
if (std::find(houseData->_storageAccessList.begin(), houseData->_storageAccessList.end(), targetId) != houseData->_storageAccessList.end())
|
|
{
|
|
m_pPlayer->SendText(csprintf("%s is already in the storage access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText(csprintf("You add %s to your dwelling's storage access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
houseData->_storageAccessList.push_back(targetId);
|
|
houseData->Save();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto iter = std::find(houseData->_storageAccessList.begin(), houseData->_storageAccessList.end(), targetId);
|
|
if (iter != houseData->_storageAccessList.end())
|
|
{
|
|
m_pPlayer->SendText(csprintf("You remove %s from your dwelling's storage access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
houseData->_storageAccessList.erase(iter);
|
|
houseData->Save();
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->SendText(csprintf("%s is not in the storage access list", name.c_str()), LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::GetHousesAvailable(uint32_t houseType)
|
|
{
|
|
std::tuple<int, std::list<uint32_t>> houses = g_pHouseManager->GetAvailableHouses(static_cast<HouseType>(houseType));
|
|
|
|
BinaryWriter msg;
|
|
msg.Write<uint32_t>(0x271);
|
|
msg.Write<uint32_t>(houseType);
|
|
msg.Write<uint32_t>(std::get<1>(houses).size());
|
|
for (auto ah : std::get<1>(houses))
|
|
{
|
|
msg.Write<uint32_t>(ah);
|
|
}
|
|
msg.Write<uint32_t>(std::get<0>(houses));
|
|
|
|
m_pPlayer->SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
void CClientEvents::HouseAddOrRemoveAllegianceToAccessList(bool isAdd)
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (houseData->_allegianceAccess != isAdd)
|
|
{
|
|
houseData->_allegianceAccess = isAdd;
|
|
if (isAdd)
|
|
m_pPlayer->SendText("You have granted your monarchy access to your dwelling.", LTT_DEFAULT);
|
|
else
|
|
m_pPlayer->SendText("You have revoked access to your dwelling to your monarchy.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
if (isAdd)
|
|
m_pPlayer->SendText("The monarchy already has access to your dwelling.", LTT_DEFAULT);
|
|
else
|
|
m_pPlayer->SendText("The monarchy did not have access to your dwelling.", LTT_DEFAULT);
|
|
}
|
|
houseData->Save();
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseAddOrRemoveAllegianceToStorageList(bool isAdd)
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
if (houseData->_allegianceStorageAccess != isAdd)
|
|
{
|
|
houseData->_allegianceStorageAccess = isAdd;
|
|
if (isAdd)
|
|
m_pPlayer->SendText("You have granted your monarchy access to your storage.", LTT_DEFAULT);
|
|
else
|
|
m_pPlayer->SendText("You have revoked storage access to your monarchy.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
if (isAdd)
|
|
m_pPlayer->SendText("The monarchy already has storage access in your dwelling.", LTT_DEFAULT);
|
|
else
|
|
m_pPlayer->SendText("The monarchy did not have storage access in your dwelling.", LTT_DEFAULT);
|
|
}
|
|
houseData->Save();
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseClearAccessList()
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
houseData->_everyoneAccess = false;
|
|
houseData->_allegianceAccess = false;
|
|
if (houseData->_accessList.empty())
|
|
{
|
|
houseData->_accessList.clear();
|
|
houseData->Save();
|
|
m_pPlayer->SendText("Your clear the dwelling's access list.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("There's no one in the dwelling's access list.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::HouseClearStorageAccess()
|
|
{
|
|
uint32_t houseId = m_pPlayer->InqDIDQuality(HOUSEID_DID, 0);
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerId == m_pPlayer->GetID())
|
|
{
|
|
houseData->_everyoneStorageAccess = false;
|
|
houseData->_allegianceStorageAccess = false;
|
|
if (houseData->_storageAccessList.empty())
|
|
{
|
|
houseData->_storageAccessList.clear();
|
|
houseData->Save();
|
|
m_pPlayer->SendText("Your clear the dwelling's storage list.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("There's no one in the dwelling's storage list.", LTT_DEFAULT); //todo: made up message.
|
|
}
|
|
else
|
|
m_pPlayer->SendText("Only the character who owns the house may use this command.", LTT_DEFAULT);
|
|
}
|
|
else
|
|
m_pPlayer->SendText("You do not own a house.", LTT_DEFAULT);
|
|
}
|
|
|
|
void CClientEvents::NoLongerViewingContents(uint32_t container_id)
|
|
{
|
|
if (CWeenieObject *remoteContainerObj = g_pWorld->FindObject(container_id))
|
|
{
|
|
if (CContainerWeenie *remoteContainer = remoteContainerObj->AsContainer())
|
|
{
|
|
remoteContainer->HandleNoLongerViewing(m_pPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CClientEvents::ChangePlayerOption(PlayerOptions option, bool value)
|
|
{
|
|
auto changeCharOption = [&](uint32_t optionBit)
|
|
{
|
|
m_pPlayer->_playerModule.options_ &= ~optionBit;
|
|
if (value)
|
|
m_pPlayer->_playerModule.options_ |= optionBit;
|
|
};
|
|
|
|
auto changeCharOption2 = [&](uint32_t optionBit)
|
|
{
|
|
m_pPlayer->_playerModule.options2_ &= ~optionBit;
|
|
if (value)
|
|
m_pPlayer->_playerModule.options2_ |= optionBit;
|
|
};
|
|
|
|
switch (option)
|
|
{
|
|
case AutoRepeatAttack_PlayerOption:
|
|
changeCharOption(AutoRepeatAttack_CharacterOption);
|
|
break;
|
|
|
|
case IgnoreAllegianceRequests_PlayerOption:
|
|
changeCharOption(IgnoreAllegianceRequests_CharacterOption);
|
|
break;
|
|
|
|
case IgnoreFellowshipRequests_PlayerOption:
|
|
changeCharOption(IgnoreFellowshipRequests_CharacterOption);
|
|
break;
|
|
|
|
case IgnoreTradeRequests_PlayerOption:
|
|
changeCharOption(IgnoreAllegianceRequests_CharacterOption);
|
|
break;
|
|
|
|
case DisableMostWeatherEffects_PlayerOption:
|
|
changeCharOption(DisableMostWeatherEffects_CharacterOption);
|
|
break;
|
|
|
|
case PersistentAtDay_PlayerOption:
|
|
changeCharOption2(PersistentAtDay_CharacterOptions2);
|
|
break;
|
|
|
|
case AllowGive_PlayerOption:
|
|
changeCharOption(AllowGive_CharacterOption);
|
|
break;
|
|
|
|
case ViewCombatTarget_PlayerOption:
|
|
changeCharOption(ViewCombatTarget_CharacterOption);
|
|
break;
|
|
|
|
case ShowTooltips_PlayerOption:
|
|
changeCharOption(ShowTooltips_CharacterOption);
|
|
break;
|
|
|
|
case UseDeception_PlayerOption:
|
|
changeCharOption(UseDeception_CharacterOption);
|
|
break;
|
|
|
|
case ToggleRun_PlayerOption:
|
|
changeCharOption(ToggleRun_CharacterOption);
|
|
break;
|
|
|
|
case StayInChatMode_PlayerOption:
|
|
changeCharOption(StayInChatMode_CharacterOption);
|
|
break;
|
|
|
|
case AdvancedCombatUI_PlayerOption:
|
|
changeCharOption(AdvancedCombatUI_CharacterOption);
|
|
break;
|
|
|
|
case AutoTarget_PlayerOption:
|
|
changeCharOption(AutoTarget_CharacterOption);
|
|
break;
|
|
|
|
case VividTargetingIndicator_PlayerOption:
|
|
changeCharOption(VividTargetingIndicator_CharacterOption);
|
|
break;
|
|
|
|
case FellowshipShareXP_PlayerOption:
|
|
changeCharOption(FellowshipShareXP_CharacterOption);
|
|
break;
|
|
|
|
case AcceptLootPermits_PlayerOption:
|
|
changeCharOption(AcceptLootPermits_PlayerOption);
|
|
break;
|
|
|
|
case FellowshipShareLoot_PlayerOption:
|
|
changeCharOption(FellowshipShareLoot_CharacterOption);
|
|
break;
|
|
|
|
case FellowshipAutoAcceptRequests_PlayerOption:
|
|
changeCharOption(FellowshipAutoAcceptRequests_PlayerOption);
|
|
break;
|
|
|
|
case SideBySideVitals_PlayerOption:
|
|
changeCharOption(SideBySideVitals_CharacterOption);
|
|
break;
|
|
|
|
case CoordinatesOnRadar_PlayerOption:
|
|
changeCharOption(CoordinatesOnRadar_CharacterOption);
|
|
break;
|
|
|
|
case SpellDuration_PlayerOption:
|
|
changeCharOption(SpellDuration_CharacterOption);
|
|
break;
|
|
|
|
case DisableHouseRestrictionEffects_PlayerOption:
|
|
changeCharOption(DisableHouseRestrictionEffects_PlayerOption);
|
|
break;
|
|
|
|
case DragItemOnPlayerOpensSecureTrade_PlayerOption:
|
|
changeCharOption(DragItemOnPlayerOpensSecureTrade_CharacterOption);
|
|
break;
|
|
|
|
case DisplayAllegianceLogonNotifications_PlayerOption:
|
|
changeCharOption(DisplayAllegianceLogonNotifications_CharacterOption);
|
|
break;
|
|
|
|
case UseChargeAttack_PlayerOption:
|
|
changeCharOption(UseChargeAttack_CharacterOption);
|
|
break;
|
|
|
|
case UseCraftSuccessDialog_PlayerOption:
|
|
changeCharOption(UseCraftSuccessDialog_CharacterOption);
|
|
break;
|
|
|
|
case HearAllegianceChat_PlayerOption:
|
|
changeCharOption(HearAllegianceChat_CharacterOption);
|
|
|
|
if (value)
|
|
SendText("Joined Allegiance Channel!", LTT_CHANNEL_SEND);
|
|
else
|
|
SendText("Left Allegiance Channel!", LTT_CHANNEL_SEND);
|
|
break;
|
|
|
|
case DisplayDateOfBirth_PlayerOption:
|
|
changeCharOption2(DisplayDateOfBirth_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayAge_PlayerOption:
|
|
changeCharOption2(DisplayAge_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayChessRank_PlayerOption:
|
|
changeCharOption2(DisplayChessRank_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayFishingSkill_PlayerOption:
|
|
changeCharOption2(DisplayFishingSkill_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayNumberDeaths_PlayerOption:
|
|
changeCharOption2(DisplayNumberDeaths_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayTimeStamps_PlayerOption:
|
|
changeCharOption2(TimeStamp_CharacterOptions2);
|
|
break;
|
|
|
|
case SalvageMultiple_PlayerOption:
|
|
changeCharOption(SalvageMultiple_CharacterOptions2);
|
|
break;
|
|
|
|
case HearGeneralChat_PlayerOption:
|
|
changeCharOption2(HearGeneralChat_CharacterOptions2);
|
|
|
|
if (value)
|
|
SendText("Joined General Channel!", LTT_CHANNEL_SEND);
|
|
else
|
|
SendText("Left General Channel!", LTT_CHANNEL_SEND);
|
|
break;
|
|
|
|
case HearTradeChat_PlayerOption:
|
|
changeCharOption2(HearTradeChat_CharacterOptions2);
|
|
|
|
if (value)
|
|
SendText("Joined Trade Channel!", LTT_CHANNEL_SEND);
|
|
else
|
|
SendText("Left Trade Channel!", LTT_CHANNEL_SEND);
|
|
|
|
break;
|
|
|
|
case HearLFGChat_PlayerOption:
|
|
changeCharOption2(HearLFGChat_CharacterOptions2);
|
|
|
|
if (value)
|
|
SendText("Joined LFG Channel!", LTT_CHANNEL_SEND);
|
|
else
|
|
SendText("Left LFG Channel!", LTT_CHANNEL_SEND);
|
|
break;
|
|
|
|
case HearRoleplayChat_PlayerOption:
|
|
changeCharOption2(HearRoleplayChat_CharacterOptions2);
|
|
|
|
if (value)
|
|
SendText("Joined Roleplay Channel!", LTT_CHANNEL_SEND);
|
|
else
|
|
SendText("Left Roleplay Channel!", LTT_CHANNEL_SEND);
|
|
break;
|
|
|
|
case AppearOffline_PlayerOption:
|
|
changeCharOption2(AppearOffline_CharacterOptions2);
|
|
break;
|
|
|
|
case DisplayNumberCharacterTitles_PlayerOption:
|
|
changeCharOption2(DisplayNumberCharacterTitles_CharacterOptions2);
|
|
break;
|
|
|
|
case MainPackPreferred_PlayerOption:
|
|
changeCharOption2(MainPackPreferred_CharacterOptions2);
|
|
break;
|
|
|
|
case LeadMissileTargets_PlayerOption:
|
|
changeCharOption2(LeadMissileTargets_CharacterOptions2);
|
|
break;
|
|
|
|
case UseFastMissiles_PlayerOption:
|
|
changeCharOption2(UseFastMissiles_CharacterOptions2);
|
|
break;
|
|
|
|
case FilterLanguage_PlayerOption:
|
|
changeCharOption2(FilterLanguage_CharacterOptions2);
|
|
break;
|
|
|
|
case ConfirmVolatileRareUse_PlayerOption:
|
|
changeCharOption2(ConfirmVolatileRareUse_CharacterOptions2);
|
|
break;
|
|
|
|
case HearSocietyChat_PlayerOption:
|
|
changeCharOption2(HearSocietyChat_CharacterOptions2);
|
|
break;
|
|
|
|
case ShowHelm_PlayerOption:
|
|
changeCharOption2(ShowHelm_CharacterOptions2);
|
|
break;
|
|
|
|
case DisableDistanceFog_PlayerOption:
|
|
changeCharOption2(DisableDistanceFog_CharacterOptions2);
|
|
break;
|
|
|
|
case UseMouseTurning_PlayerOption:
|
|
changeCharOption2(UseMouseTurning_CharacterOptions2);
|
|
break;
|
|
|
|
case ShowCloak_PlayerOption:
|
|
changeCharOption2(ShowCloak_CharacterOptions2);
|
|
break;
|
|
|
|
case LockUI_PlayerOption:
|
|
changeCharOption2(LockUI_CharacterOptions2);
|
|
break;
|
|
|
|
case TotalNumberOfPlayerOptions_PlayerOption:
|
|
changeCharOption2(TotalNumberOfPlayerOptions_PlayerOption);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CClientEvents::CheckForChatSpam()
|
|
{
|
|
if (_next_chat_allowed > Timer::cur_time)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_next_chat_interval < Timer::cur_time)
|
|
{
|
|
_next_chat_interval = Timer::cur_time + 8.0;
|
|
_num_chat_sent = 0;
|
|
}
|
|
|
|
_num_chat_sent++;
|
|
if (_num_chat_sent > 8)
|
|
{
|
|
_next_chat_allowed = Timer::cur_time + 10.0;
|
|
m_pPlayer->SendText("You are sending too many messages and have been temporarily muted.", LTT_DEFAULT);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CClientEvents::SetCharacterSquelchSetting(bool squelchSet, uint32_t squelchPlayer, std::string squelchName, BYTE squelchChatType, bool account)
|
|
{
|
|
if (squelchPlayer == 0 && squelchName.size() == 0)
|
|
return;
|
|
|
|
CharacterSquelch_t squelchSettings;
|
|
if (squelchPlayer == 0)
|
|
{
|
|
uint32_t playerId = g_pWorld->GetPlayerId(squelchName.c_str(), true);
|
|
squelchSettings.squelched_id = playerId;
|
|
squelchSettings.account_id = g_pDBIO->GetPlayerAccountId(playerId);
|
|
}
|
|
else
|
|
{
|
|
squelchSettings.squelched_id = squelchPlayer;
|
|
squelchSettings.account_id = g_pDBIO->GetPlayerAccountId(squelchPlayer);
|
|
}
|
|
|
|
if (squelchSettings.squelched_id == 0)
|
|
return;
|
|
|
|
switch (squelchChatType)
|
|
{
|
|
case SquelchTypes::Speech_Squelch:
|
|
squelchSettings.isSpeech = squelchSet;
|
|
break;
|
|
case 0x03:
|
|
squelchSettings.isTell = squelchSet;
|
|
break;
|
|
case 0x06:
|
|
squelchSettings.isCombat = squelchSet;
|
|
break;
|
|
case 0x07:
|
|
squelchSettings.isMagic = squelchSet;
|
|
break;
|
|
case 0x0C:
|
|
squelchSettings.isEmote = squelchSet;
|
|
break;
|
|
case 0x0D:
|
|
squelchSettings.isAdvancement = squelchSet;
|
|
break;
|
|
case 0x10:
|
|
squelchSettings.isAppraisal = squelchSet;
|
|
break;
|
|
case 0x11:
|
|
squelchSettings.isSpellcasting = squelchSet;
|
|
break;
|
|
case 0x12:
|
|
squelchSettings.isAllegiance = squelchSet;
|
|
break;
|
|
case 0x13:
|
|
squelchSettings.isFellowship = squelchSet;
|
|
break;
|
|
case 0x15:
|
|
squelchSettings.isCombatEnemy = squelchSet;
|
|
break;
|
|
case 0x17:
|
|
squelchSettings.isRecall = squelchSet;
|
|
break;
|
|
case 0x18:
|
|
squelchSettings.isCrafting = squelchSet;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (squelchChatType == SquelchTypes::AllChannels_Squelch)
|
|
{
|
|
squelchSettings.isSpeech = true;
|
|
squelchSettings.isTell = true;
|
|
squelchSettings.isCombat = true;
|
|
squelchSettings.isMagic = true;
|
|
squelchSettings.isEmote = true;
|
|
squelchSettings.isAdvancement = true;
|
|
squelchSettings.isAppraisal = true;
|
|
squelchSettings.isSpellcasting = true;
|
|
squelchSettings.isAllegiance = true;
|
|
squelchSettings.isFellowship = true;
|
|
squelchSettings.isCombatEnemy = true;
|
|
squelchSettings.isRecall = true;
|
|
squelchSettings.isCrafting = true;
|
|
}
|
|
|
|
if (squelchSet)
|
|
{
|
|
if (!g_pDBIO->SaveCharacterSquelch(m_pPlayer->GetID(), squelchSettings))
|
|
{
|
|
DEBUG_DATA << "Failed to save squelch data";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!g_pDBIO->RemoveCharacterSquelch(m_pPlayer->GetID(), squelchSettings))
|
|
{
|
|
DEBUG_DATA << "Failed to remove squelch data";
|
|
}
|
|
}
|
|
|
|
m_pPlayer->LoadSquelches();
|
|
SendSquelchDB();
|
|
}
|
|
|
|
void CClientEvents::PKLiteEnable()
|
|
{
|
|
double dt = 0;
|
|
int it = 0;
|
|
if (m_pPlayer->m_Qualities.InqFloat(PK_TIMESTAMP_FLOAT, dt) ||
|
|
(m_pPlayer->m_Qualities.InqInt(PLAYER_KILLER_STATUS_INT, it) && (it & PKStatusEnum::PK_PKStatus)))
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PK_SWITCH_PKLITE_ON_PK);
|
|
return;
|
|
}
|
|
|
|
if (m_pPlayer->IsBusyOrInAction())
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
CPlayerWeenie *player = m_pPlayer;
|
|
motion_use_event *pkl = new motion_use_event(Motion_EnterPKLite, [player](uint32_t motion)
|
|
{
|
|
player->m_Qualities.SetInt(PLAYER_KILLER_STATUS_INT, PKStatusEnum::PKLite_PKStatus);
|
|
player->NotifyIntStatUpdated(PLAYER_KILLER_STATUS_INT, false);
|
|
player->SendText(csprintf("%s is looking for a fight!", player->GetName().c_str()), LTT_GENERAL_CHANNEL);
|
|
player->NotifyWeenieError(WERROR_PK_SWITCH_PKLITE_ON);
|
|
});
|
|
player->ExecuteUseEvent(pkl);
|
|
}
|
|
|
|
// This is it!
|
|
void CClientEvents::ProcessEvent(BinaryReader *pReader)
|
|
{
|
|
if (!m_pPlayer)
|
|
return;
|
|
|
|
if (m_pPlayer->IsPkLogging())
|
|
return;
|
|
|
|
uint32_t dwSequence = pReader->ReadUInt32();
|
|
uint32_t dwEvent = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) return;
|
|
|
|
#ifdef _DEBUG
|
|
string enumString = "";
|
|
switch (dwEvent)
|
|
{
|
|
case 0x0005: enumString = "CHANGE_PLAYER_OPTION "; break;
|
|
case 0x0008: enumString = "MELEE_ATTACK "; break;
|
|
case 0x000A: enumString = "MISSILE_ATTACK "; break;
|
|
case 0x000F: enumString = "SET_AFK_MODE "; break;
|
|
case 0x0010: enumString = "SET_AFK_MESSAGE "; break;
|
|
case 0x0015: enumString = "TEXT_CLIENT "; break;
|
|
case 0x0017: enumString = "REMOVE_FRIEND "; break;
|
|
case 0x0018: enumString = "ADD_FRIEND "; break;
|
|
case 0x0019: enumString = "STORE_ITEM "; break;
|
|
case 0x001A: enumString = "EQUIP_ITEM "; break;
|
|
case 0x001B: enumString = "DROP_ITEM "; break;
|
|
case 0x001D: enumString = "ALLEGIANCE_SWEAR "; break;
|
|
case 0x001E: enumString = "ALLEGIANCE_BREAK "; break;
|
|
case 0x001F: enumString = "ALLEGIANCE_SEND_UPDATES "; break;
|
|
case 0x0025: enumString = "CLEAR_FRIENDS "; break;
|
|
case 0x0026: enumString = "RECALL_PKL_ARENA "; break;
|
|
case 0x0027: enumString = "RECALL_PK_ARENA "; break;
|
|
case 0x002C: enumString = "SET_DISPLAY_TITLE "; break;
|
|
case 0x0275: enumString = "CONFIRMATION_RESPONSE "; break;
|
|
case 0x027D: enumString = "UST_SALVAGE_REQUEST "; break;
|
|
case 0x0030: enumString = "ALLEGIANCE_QUERY_NAME "; break;
|
|
case 0x0031: enumString = "ALLEGIANCE_CLEAR_NAME "; break;
|
|
case 0x0032: enumString = "SEND_TELL_BY_GUID "; break;
|
|
case 0x0033: enumString = "ALLEGIANCE_SET_NAME "; break;
|
|
case 0x0035: enumString = "USE_ITEM_EX "; break;
|
|
case 0x0036: enumString = "USE_OBJECT "; break;
|
|
case 0x003B: enumString = "ALLEGIANCE_SET_OFFICER "; break;
|
|
case 0x003C: enumString = "ALLEGIANCE_SET_OFFICER_TITLE "; break;
|
|
case 0x003D: enumString = "ALLEGIANCE_LIST_OFFICER_TITLES "; break;
|
|
case 0x003E: enumString = "ALLEGIANCE_CLEAR_OFFICER_TITLES "; break;
|
|
case 0x003F: enumString = "ALLEGIANCE_LOCK_ACTION "; break;
|
|
case 0x0040: enumString = "ALLEGIANCE_APPROVED_VASSAL "; break;
|
|
case 0x0041: enumString = "ALLEGIANCE_CHAT_GAG "; break;
|
|
case 0x0042: enumString = "ALLEGIANCE_HOUSE_ACTION "; break;
|
|
case 0x0044: enumString = "SPEND_XP_VITALS "; break;
|
|
case 0x0045: enumString = "SPEND_XP_ATTRIBUTES "; break;
|
|
case 0x0046: enumString = "SPEND_XP_SKILLS "; break;
|
|
case 0x0047: enumString = "SPEND_SKILL_CREDITS "; break;
|
|
case 0x0048: enumString = "CAST_UNTARGETED_SPELL "; break;
|
|
case 0x004A: enumString = "CAST_TARGETED_SPELL "; break;
|
|
case 0x0053: enumString = "CHANGE_COMBAT_STANCE "; break;
|
|
case 0x0054: enumString = "STACKABLE_MERGE "; break;
|
|
case 0x0055: enumString = "STACKABLE_SPLIT_TO_CONTAINER "; break;
|
|
case 0x0056: enumString = "STACKABLE_SPLIT_TO_3D "; break;
|
|
case 0x019B: enumString = "STACKABLE_SPLIT_TO_WIELD "; break;
|
|
case 0x0058: enumString = "SQUELCH_CHARACTER_MODIFY "; break;
|
|
case 0x0059: enumString = "SQUELCH_ACCOUNT_MODIFY "; break;
|
|
case 0x005B: enumString = "SQUELCH_GLOBAL_MODIFY "; break;
|
|
case 0x005D: enumString = "SEND_TELL_BY_NAME "; break;
|
|
case 0x005F: enumString = "BUY_FROM_VENDOR "; break;
|
|
case 0x0060: enumString = "SELL_TO_VENDOR "; break;
|
|
case 0x0063: enumString = "RECALL_LIFESTONE "; break;
|
|
case 0x00A1: enumString = "LOGIN_COMPLETE "; break;
|
|
case 0x00A2: enumString = "FELLOW_CREATE "; break;
|
|
case 0x00A3: enumString = "FELLOW_QUIT "; break;
|
|
case 0x00A4: enumString = "FELLOW_DISMISS "; break;
|
|
case 0x00A5: enumString = "FELLOW_RECRUIT "; break;
|
|
case 0x00A6: enumString = "FELLOW_UPDATE "; break;
|
|
case 0x00AA: enumString = "BOOK_ADD_PAGE "; break;
|
|
case 0x00AB: enumString = "BOOK_MODIFY_PAGE "; break;
|
|
case 0x00AC: enumString = "BOOK_DATA "; break;
|
|
case 0x00AD: enumString = "BOOK_DELETE_PAGE "; break;
|
|
case 0x00AE: enumString = "BOOK_PAGE_DATA "; break;
|
|
case 0x00CD: enumString = "GIVE_OBJECT "; break;
|
|
case 0x00BF: enumString = "INSCRIBE "; break;
|
|
case 0x00C8: enumString = "APPRAISE "; break;
|
|
case 0x00D6: enumString = "ADMIN_TELEPORT "; break;
|
|
case 0x0140: enumString = "ABUSE_LOG_REQUEST "; break;
|
|
case 0x0145: enumString = "CHANNEL_ADD "; break;
|
|
case 0x0146: enumString = "CHANNEL_REMOVE "; break;
|
|
case 0x0147: enumString = "CHANNEL_TEXT "; break;
|
|
case 0x0148: enumString = "CHANNEL_LIST "; break;
|
|
case 0x0149: enumString = "CHANNEL_INDEX "; break;
|
|
case 0x0195: enumString = "NO_LONGER_VIEWING_CONTAINER "; break;
|
|
case 0x019C: enumString = "ADD_ITEM_SHORTCUT "; break;
|
|
case 0x019D: enumString = "REMOVE_ITEM_SHORTCUT "; break;
|
|
case 0x01A1: enumString = "CHARACTER_OPTIONS "; break;
|
|
case 0x01A8: enumString = "SPELLBOOK_REMOVE "; break;
|
|
case 0x01B7: enumString = "CANCEL_ATTACK "; break;
|
|
case 0x01BF: enumString = "QUERY_HEALTH "; break;
|
|
case 0x01C2: enumString = "QUERY_AGE "; break;
|
|
case 0x01C4: enumString = "QUERY_BIRTH "; break;
|
|
case 0x01DF: enumString = "TEXT_INDIRECT "; break;
|
|
case 0x01E1: enumString = "TEXT_EMOTE "; break;
|
|
case 0x01E3: enumString = "ADD_TO_SPELLBAR "; break;
|
|
case 0x01E4: enumString = "REMOVE_FROM_SPELLBAR "; break;
|
|
case 0x01E9: enumString = "PING "; break;
|
|
case 0x1F6: enumString = "TRADE_OPEN "; break;
|
|
case 0x1F7: enumString = "TRADE_CLOSE "; break;
|
|
case 0x1F8: enumString = "TRADE_ADD "; break;
|
|
case 0x1FA: enumString = "TRADE_ACCEPT "; break;
|
|
case 0x1FB: enumString = "TRADE_DECLINE "; break;
|
|
case 0x204: enumString = "TRADE_RESET "; break;
|
|
case 0x0216: enumString = "CLEAR_PLAYER_CONSENT_LIST "; break;
|
|
case 0x0217: enumString = "DISPLAY_PLAYER_CONSENT_LIST "; break;
|
|
case 0x0218: enumString = "REMOVE_FROM_PLAYER_CONSENT_LIST "; break;
|
|
case 0x0219: enumString = "ADD_PLAYER_PERMISSION "; break;
|
|
case 0x021A: enumString = "REMOVE_PLAYER_PERMISSION "; break;
|
|
case 0x021C: enumString = "HOUSE_BUY "; break;
|
|
case 0x021F: enumString = "HOUSE_ABANDON "; break;
|
|
case 0x21E: enumString = "HOUSE_OF_PLAYER_QUERY "; break;
|
|
case 0x0221: enumString = "HOUSE_RENT "; break;
|
|
case 0x0224: enumString = "DESIRED_COMPS_SET "; break;
|
|
case 0x0245: enumString = "HOUSE_ADD_GUEST "; break;
|
|
case 0x0246: enumString = "HOUSE_REMOVE_GUEST "; break;
|
|
case 0x0247: enumString = "HOUSE_SET_OPEN_ACCESS "; break;
|
|
case 0x0249: enumString = "HOUSE_CHANGE_STORAGE_PERMISSIONS "; break;
|
|
case 0x024A: enumString = "HOUSE_BOOT_GUEST "; break;
|
|
case 0x024C: enumString = "HOUSE_CLEAR_STORAGE_PERMISSIONS "; break;
|
|
case 0x024D: enumString = "HOUSE_GUEST_LIST "; break;
|
|
case 0x0254: enumString = "ALLEGIANCE_SET_MOTD "; break;
|
|
case 0x0255: enumString = "ALLEGIANCE_QUERY_MOTD "; break;
|
|
case 0x0256: enumString = "ALLEGIANCE_CLEAR_MOTD "; break;
|
|
case 0x0258: enumString = "HOUSE_QUERY_SLUMLORD "; break;
|
|
case 0x025C: enumString = "HOUSE_SET_OPEN_STORAGE_ACCESS "; break;
|
|
case 0x025E: enumString = "HOUSE_REMOVE_ALL_GUESTS "; break;
|
|
case 0x025F: enumString = "HOUSE_BOOT_ALL "; break;
|
|
case 0x0262: enumString = "RECALL_HOUSE "; break;
|
|
case 0x0263: enumString = "ITEM_MANA_REQUEST "; break;
|
|
case 0x0266: enumString = "HOUSE_SET_HOOKS_VISIBILITY "; break;
|
|
case 0x0267: enumString = "HOUSE_CHANGE_ALLEGIANCE_GUEST_PERMISSIONS "; break;
|
|
case 0x0268: enumString = "HOUSE_CHANGE_ALLEGIANCE_STORAGE_PERMISSIONS "; break;
|
|
case 0x0269: enumString = "CHESS_JOIN "; break;
|
|
case 0x026A: enumString = "CHESS_QUIT "; break;
|
|
case 0x026B: enumString = "CHESS_MOVE "; break;
|
|
case 0x026D: enumString = "CHESS_PASS "; break;
|
|
case 0x026E: enumString = "CHESS_STALEMATE "; break;
|
|
case 0x0270: enumString = "HOUSE_LIST_AVAILABLE "; break;
|
|
case 0x0277: enumString = "ALLEGIANCE_BOOT_PLAYER "; break;
|
|
case 0x0278: enumString = "RECALL_HOUSE_MANSION "; break;
|
|
case 0x0279: enumString = "DIE_COMMAND "; break;
|
|
case 0x027B: enumString = "ALLEGIANCE_INFO_REQUEST "; break;
|
|
case 0x0286: enumString = "SPELLBOOK_FILTERS "; break;
|
|
case 0x028D: enumString = "RECALL_MARKET "; break;
|
|
case 0x028F: enumString = "PKLITE "; break;
|
|
case 0x0290: enumString = "FELLOW_ASSIGN_NEW_LEADER "; break;
|
|
case 0x0291: enumString = "FELLOW_CHANGE_OPENNESS "; break;
|
|
case 0x02A0: enumString = "ALLEGIANCE_CHAT_BOOT "; break;
|
|
case 0x02A1: enumString = "ALLEGIANCE_ADD_PLAYER_BAN "; break;
|
|
case 0x02A2: enumString = "ALLEGIANCE_REMOVE_PLAYER_BAN "; break;
|
|
case 0x02A3: enumString = "ALLEGIANCE_LIST_BANS "; break;
|
|
case 0x02A5: enumString = "ALLEGIANCE_REMOVE_OFFICER "; break;
|
|
case 0x02A6: enumString = "ALLEGIANCE_LIST_OFFICERS "; break;
|
|
case 0x02A7: enumString = "ALLEGIANCE_CLEAR_OFFICERS "; break;
|
|
case 0x02AB: enumString = "RECALL_ALLEGIANCE_HOMETOWN "; break;
|
|
case 0x0311: enumString = "FINISH_BARBER "; break;
|
|
case 0x0316: enumString = "CONTRACT_ABANDON "; break;
|
|
case 0xF61B: enumString = "MOVEMENT_JUMP "; break;
|
|
case 0xF61C: enumString = "MOVEMENT_MOVE_TO_STATE "; break;
|
|
case 0xF61E: enumString = "MOVEMENT_DO_MOVEMENT_COMMAND "; break;
|
|
case 0xF649: enumString = "MOVEMENT_TURN_TO "; break;
|
|
case 0xF661: enumString = "MOVEMENT_STOP "; break;
|
|
case 0xF752: enumString = "MOVEMENT_AUTONOMY_LEVEL "; break;
|
|
case 0xF753: enumString = "MOVEMENT_AUTONOMOUS_POSITION "; break;
|
|
case 0xF7C9: enumString = "MOVEMENT_JUMP_NON_AUTONOMOUS "; break;
|
|
default:
|
|
DEBUG_DATA << "Processing Unknown event:" << dwEvent;
|
|
break;
|
|
};
|
|
|
|
DEBUG_DATA << "Processing event:" << enumString;
|
|
#endif
|
|
|
|
|
|
switch (dwEvent)
|
|
{
|
|
case CHANGE_PLAYER_OPTION: // Change player option
|
|
{
|
|
uint32_t option = pReader->ReadUInt32();
|
|
uint32_t value = pReader->ReadUInt32();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
ChangePlayerOption((PlayerOptions)option, value ? true : false);
|
|
break;
|
|
}
|
|
case MELEE_ATTACK: // Melee Attack
|
|
{
|
|
uint32_t dwTarget = pReader->ReadUInt32();
|
|
uint32_t dwHeight = pReader->ReadUInt32();
|
|
float flPower = pReader->ReadFloat();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
Attack(dwTarget, dwHeight, flPower);
|
|
break;
|
|
}
|
|
case MISSILE_ATTACK: // Missile Attack
|
|
{
|
|
uint32_t target = pReader->ReadUInt32();
|
|
uint32_t height = pReader->ReadUInt32();
|
|
float power = pReader->ReadFloat();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
MissileAttack(target, height, power);
|
|
break;
|
|
}
|
|
case SET_AFK_MODE:
|
|
{
|
|
// Read: bool afk - Whether user is afk
|
|
break;
|
|
}
|
|
case SET_AFK_MESSAGE:
|
|
{
|
|
// Read: string message
|
|
break;
|
|
}
|
|
case TEXT_CLIENT: //Client Text
|
|
{
|
|
char *szText = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
ClientText(szText);
|
|
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case REMOVE_FRIEND:
|
|
{
|
|
uint32_t dwFriend = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->RemoveFriend(dwFriend);
|
|
|
|
break;
|
|
}
|
|
case ADD_FRIEND:
|
|
{
|
|
char *szFriend = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->AddFriend(std::string(szFriend));
|
|
|
|
break;
|
|
}
|
|
case STORE_ITEM: //Store Item
|
|
{
|
|
uint32_t dwItemID = pReader->ReadUInt32();
|
|
uint32_t dwContainer = pReader->ReadUInt32();
|
|
uint32_t dwSlot = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->MoveItemToContainer(dwItemID, dwContainer, (char)dwSlot);
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case EQUIP_ITEM: //Equip Item
|
|
{
|
|
uint32_t dwItemID = pReader->ReadUInt32();
|
|
uint32_t dwCoverage = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->MoveItemToWield(dwItemID, dwCoverage);
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case DROP_ITEM: //Drop Item
|
|
{
|
|
uint32_t dwItemID = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->MoveItemTo3D(dwItemID);
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case ALLEGIANCE_SWEAR: // Swear Allegiance request
|
|
{
|
|
MAllegianceSwear_001D msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_BREAK: // Break Allegiance request
|
|
{
|
|
MAllegianceBreak_001E msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_SEND_UPDATES: // Allegiance Update request
|
|
{
|
|
MAllegianceUpdate_001F msg(m_pClient);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case CLEAR_FRIENDS:
|
|
{
|
|
// Nothing to read
|
|
break;
|
|
}
|
|
case RECALL_PKL_ARENA:
|
|
{
|
|
PKLArenaRecall();
|
|
break;
|
|
}
|
|
case RECALL_PK_ARENA:
|
|
{
|
|
PKArenaRecall();
|
|
break;
|
|
}
|
|
case SET_DISPLAY_TITLE:
|
|
{
|
|
uint32_t dwTitleID = pReader->ReadUInt32();
|
|
m_pPlayer->SetTitle(dwTitleID);
|
|
break;
|
|
}
|
|
case CONFIRMATION_RESPONSE: // confirmation response
|
|
{
|
|
// ConfirmationTypes: SwearAllegiance 1, AlterSkill 2, AlterAttribute 3, Fellowship 4, Craft 5, Augmentation 6, YesNo 7
|
|
uint32_t confirmType = pReader->ReadUInt32();
|
|
int context = pReader->ReadInt32();
|
|
bool accepted = pReader->ReadInt32();
|
|
|
|
switch (confirmType)
|
|
{
|
|
case 0x05: // crafting
|
|
if (accepted)
|
|
{
|
|
m_pPlayer->UseEx(true);
|
|
}
|
|
break;
|
|
case 0x06: // Augmentation
|
|
if (accepted)
|
|
{
|
|
m_pPlayer->UseEx(true);
|
|
}
|
|
break;
|
|
case 0x07:
|
|
CWeenieObject *target = g_pWorld->FindObject(context);
|
|
|
|
if (target)
|
|
{
|
|
target->MakeEmoteManager()->ConfirmationResponse(accepted, m_pPlayer->id);
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
case UST_SALVAGE_REQUEST: // ust salvage request
|
|
{
|
|
uint32_t toolId = pReader->ReadUInt32();
|
|
|
|
PackableList<uint32_t> items;
|
|
items.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (items.size() <= 300) //just some sanity checking: 102 items in main pack + (24 * 7) items in subpacks = 270 items. 300 just to be safe.
|
|
m_pPlayer->PerformSalvaging(toolId, items);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_QUERY_NAME:
|
|
{
|
|
MAllegianceNameQuery_0030 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CLEAR_NAME:
|
|
{
|
|
MAllegianceNameClear_0031 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
|
|
case ALLEGIANCE_SET_NAME:
|
|
{
|
|
MAllegianceNameSet_0033 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case USE_ITEM_EX: //Use Item Ex
|
|
{
|
|
uint32_t dwSourceID = pReader->ReadUInt32();
|
|
uint32_t dwDestID = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
UseItemEx(dwSourceID, dwDestID);
|
|
break;
|
|
}
|
|
case USE_OBJECT: //Use Object
|
|
{
|
|
uint32_t dwEID = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
UseObject(dwEID);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_SET_OFFICER:
|
|
{
|
|
MAllegianceOfficerSet_003B msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_SET_OFFICER_TITLE:
|
|
{
|
|
MAllegianceOfficerTitleSet_003C msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_LIST_OFFICER_TITLES:
|
|
{
|
|
MAllegianceOfficerTitlesList_003D msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CLEAR_OFFICER_TITLES:
|
|
{
|
|
MAllegianceOfficerTitlesClear_003E msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_LOCK_ACTION:
|
|
{
|
|
// Read: AllegianceLockAction action - LockedOff 1, LockedOn 2, ToggleLocked 3, CheckLocked 4, DisplayBypass 5, ClearBypass 6
|
|
MAllegianceLockAction_003F msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
|
|
break;
|
|
}
|
|
case ALLEGIANCE_APPROVED_VASSAL:
|
|
{
|
|
MAllegianceApprovedVassalSet_0040 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CHAT_GAG:
|
|
{
|
|
// Read: string charName, bool on - player being gagged, whether gag is on
|
|
break;
|
|
}
|
|
case ALLEGIANCE_HOUSE_ACTION:
|
|
{
|
|
// Read: AllegianceHouseAction action - Help 1, GuestOpen 2, GuestClosed 3, StorageOpen 4, StorageClosed 5
|
|
break;
|
|
}
|
|
case SPEND_XP_VITALS: // spend XP on vitals
|
|
{
|
|
uint32_t dwAttribute2nd = pReader->ReadUInt32();
|
|
uint32_t dwXP = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SpendAttribute2ndXP((STypeAttribute2nd)dwAttribute2nd, dwXP);
|
|
break;
|
|
}
|
|
case SPEND_XP_ATTRIBUTES: // spend XP on attributes
|
|
{
|
|
uint32_t dwAttribute = pReader->ReadUInt32();
|
|
uint32_t dwXP = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SpendAttributeXP((STypeAttribute)dwAttribute, dwXP);
|
|
break;
|
|
}
|
|
case SPEND_XP_SKILLS: // spend XP on skills
|
|
{
|
|
uint32_t dwSkill = pReader->ReadUInt32();
|
|
uint32_t dwXP = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SpendSkillXP((STypeSkill)dwSkill, dwXP);
|
|
break;
|
|
}
|
|
case SPEND_SKILL_CREDITS: // spend credits to train skill
|
|
{
|
|
uint32_t dwSkill = pReader->ReadUInt32();
|
|
uint32_t dwCredits = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SpendSkillCredits((STypeSkill)dwSkill, dwCredits);
|
|
break;
|
|
}
|
|
case CAST_UNTARGETED_SPELL: // cast untargeted spell
|
|
{
|
|
uint32_t spell_id = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->TryCastSpell(m_pPlayer->GetID() /*0*/, spell_id);
|
|
break;
|
|
}
|
|
case CAST_TARGETED_SPELL: // cast targeted spell
|
|
{
|
|
uint32_t target = pReader->ReadUInt32();
|
|
uint32_t spell_id = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->TryCastSpell(target, spell_id);
|
|
break;
|
|
}
|
|
case CHANGE_COMBAT_STANCE: // Evt_Combat__ChangeCombatMode_ID "Change Combat Mode"
|
|
{
|
|
uint32_t mode = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->m_bChangingStance = true;
|
|
ChangeCombatStance((COMBAT_MODE)mode);
|
|
break;
|
|
}
|
|
case STACKABLE_MERGE: // Evt_Inventory__StackableMerge
|
|
{
|
|
uint32_t merge_from_id = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
uint32_t merge_to_id = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
uint32_t amount = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
if (!g_pWorld->IsItemInUse(merge_from_id))
|
|
{
|
|
m_pPlayer->MergeItem(merge_from_id, merge_to_id, amount);
|
|
g_pWorld->RemoveMergedItem(merge_from_id);
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->NotifyInventoryFailedEvent(merge_from_id, WERROR_OBJECT_GONE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case STACKABLE_SPLIT_TO_CONTAINER: // Evt_Inventory__StackableSplitToContainer
|
|
{
|
|
uint32_t stack_id = pReader->Read<uint32_t>();
|
|
uint32_t container_id = pReader->Read<uint32_t>();
|
|
uint32_t place = pReader->Read<uint32_t>();
|
|
uint32_t amount = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->SplitItemToContainer(stack_id, container_id, place, amount);
|
|
break;
|
|
}
|
|
case STACKABLE_SPLIT_TO_3D: // Evt_Inventory__StackableSplitTo3D
|
|
{
|
|
uint32_t stack_id = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
uint32_t amount = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
m_pPlayer->SplitItemto3D(stack_id, amount);
|
|
break;
|
|
}
|
|
case STACKABLE_SPLIT_TO_WIELD: // Evt_Inventory__StackableSplitToWield
|
|
{
|
|
uint32_t stack_id = pReader->Read<uint32_t>();
|
|
uint32_t loc = pReader->Read<uint32_t>();
|
|
uint32_t amount = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->SplitItemToWield(stack_id, loc, amount);
|
|
break;
|
|
}
|
|
case SQUELCH_CHARACTER_MODIFY:
|
|
{
|
|
// Read: bool add, uint32_t characterID, string characterName, ChatMessageType msgType
|
|
// 0 = unsquelch, 1 = squelch, ChatMessageType = enum LogTextType
|
|
|
|
bool squelchSet = pReader->Read<uint32_t>() > 0;
|
|
if (pReader->GetLastError()) break;
|
|
uint32_t squelchPlayer = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
std::string squelchName = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
BYTE squelchChatType = pReader->ReadByte();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
CPlayerWeenie* target = g_pWorld->FindPlayer(squelchName.c_str());
|
|
if (!target)
|
|
{
|
|
m_pPlayer->SendText("Player not found.", LTT_DEFAULT);
|
|
break;
|
|
}
|
|
|
|
SetCharacterSquelchSetting(squelchSet, squelchPlayer, squelchName, squelchChatType, TRUE);
|
|
|
|
break;
|
|
}
|
|
case SQUELCH_ACCOUNT_MODIFY:
|
|
{
|
|
// Read: bool add, string characterName
|
|
|
|
bool squelchSet = pReader->Read<uint32_t>() > 0;
|
|
if (pReader->GetLastError()) break;
|
|
std::string squelchName = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SetCharacterSquelchSetting(squelchSet, 0, squelchName, 0, TRUE);
|
|
|
|
break;
|
|
}
|
|
case SQUELCH_GLOBAL_MODIFY:
|
|
{
|
|
// Read: bool add, ChatMessageType msgType
|
|
|
|
break;
|
|
}
|
|
case SEND_TELL_BY_NAME: //Send Tell by Name
|
|
{
|
|
char* szText = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
char* szName = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
SendTell(szText, szName, 0);
|
|
|
|
break;
|
|
}
|
|
case SEND_TELL_BY_GUID: //Send Tell by GUID
|
|
{
|
|
char *text = pReader->ReadString();
|
|
if (pReader->GetLastError()) break;
|
|
uint32_t GUID = pReader->ReadUInt32();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
if (GUID > 0)
|
|
SendTell(text, NULL, GUID);
|
|
else
|
|
m_pPlayer->SendText("Player not found.", LTT_DEFAULT);
|
|
|
|
break;
|
|
}
|
|
case BUY_FROM_VENDOR: // Buy from Vendor
|
|
{
|
|
uint32_t vendorID = pReader->Read<uint32_t>();
|
|
uint32_t numItems = pReader->Read<uint32_t>();
|
|
if (numItems >= 300)
|
|
break;
|
|
|
|
bool error = false;
|
|
std::list<ItemProfile *> items;
|
|
|
|
for (uint32_t i = 0; i < numItems; i++)
|
|
{
|
|
ItemProfile *item = new ItemProfile();
|
|
error = item->UnPack(pReader);
|
|
items.push_back(item);
|
|
|
|
if (error || pReader->GetLastError())
|
|
break;
|
|
}
|
|
|
|
if (!error && !pReader->GetLastError())
|
|
{
|
|
TryBuyItems(vendorID, items);
|
|
}
|
|
|
|
for (auto item : items)
|
|
{
|
|
delete item;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SELL_TO_VENDOR: // Sell to Vendor
|
|
{
|
|
uint32_t vendorID = pReader->Read<uint32_t>();
|
|
uint32_t numItems = pReader->Read<uint32_t>();
|
|
if (numItems >= 300)
|
|
break;
|
|
|
|
bool error = false;
|
|
std::list<ItemProfile *> items;
|
|
|
|
for (uint32_t i = 0; i < numItems; i++)
|
|
{
|
|
std::unique_ptr<ItemProfile> item(new ItemProfile());
|
|
error = item->UnPack(pReader);
|
|
if (error || pReader->GetLastError())
|
|
break;
|
|
else
|
|
{
|
|
CWeenieObject* itemToSell = m_pPlayer->FindContainedItem(item->iid);
|
|
if (itemToSell && itemToSell->IsSellable() && !itemToSell->IsRetained())
|
|
{
|
|
items.push_back(item.release());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!error && !pReader->GetLastError())
|
|
{
|
|
TrySellItems(vendorID, items);
|
|
}
|
|
|
|
for (auto item : items)
|
|
{
|
|
delete item;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case RECALL_LIFESTONE: // Lifestone Recall
|
|
{
|
|
LifestoneRecall();
|
|
break;
|
|
}
|
|
case LOGIN_COMPLETE: // "Login Complete"
|
|
{
|
|
ExitPortal();
|
|
break;
|
|
}
|
|
case FELLOW_CREATE: // "Create Fellowship"
|
|
{
|
|
std::string name = pReader->ReadString();
|
|
int shareXP = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipCreate(name, shareXP);
|
|
break;
|
|
}
|
|
case FELLOW_QUIT: // "Quit Fellowship"
|
|
{
|
|
int disband = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipQuit(disband);
|
|
break;
|
|
}
|
|
case FELLOW_DISMISS: // "Fellowship Dismiss"
|
|
{
|
|
uint32_t dismissed = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError()) break;
|
|
|
|
TryFellowshipDismiss(dismissed);
|
|
break;
|
|
}
|
|
case FELLOW_RECRUIT: // "Fellowship Recruit"
|
|
{
|
|
uint32_t target = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipRecruit(target);
|
|
break;
|
|
}
|
|
case FELLOW_UPDATE: // "Fellowship Update"
|
|
{
|
|
int on = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipUpdate(on);
|
|
break;
|
|
}
|
|
case BOOK_ADD_PAGE:
|
|
{
|
|
// Read: uint32_t objectID - ID of book
|
|
break;
|
|
}
|
|
case BOOK_MODIFY_PAGE:
|
|
{
|
|
// Read: uint32_t objectID, int pageNum, string pageText
|
|
break;
|
|
}
|
|
case BOOK_DATA: // Request update to book data (seems to be after failed add page)
|
|
{
|
|
// Read: uint32_t objectID - ID of book
|
|
break;
|
|
}
|
|
case BOOK_DELETE_PAGE:
|
|
{
|
|
// Read: uint32_t objectID, int pageNum
|
|
break;
|
|
}
|
|
case BOOK_PAGE_DATA: // Requests data of a page of a book
|
|
{
|
|
// Read: uint32_t objectID, int pageNum
|
|
break;
|
|
}
|
|
case GIVE_OBJECT: // Give an item to someone
|
|
{
|
|
uint32_t target_id = pReader->Read<uint32_t>();
|
|
uint32_t object_id = pReader->Read<uint32_t>();
|
|
uint32_t amount = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->GiveItem(target_id, object_id, amount);
|
|
break;
|
|
}
|
|
case INSCRIBE: // "Inscribe"
|
|
{
|
|
uint32_t target_id = pReader->Read<uint32_t>();
|
|
std::string msg = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryInscribeItem(target_id, msg);
|
|
break;
|
|
}
|
|
case APPRAISE: // Identify
|
|
{
|
|
uint32_t target_id = pReader->ReadUInt32();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
Identify(target_id);
|
|
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case ADMIN_TELEPORT: // Advocate teleport (triggered by having an admin flag set, clicking the mini-map)
|
|
{
|
|
if (m_pPlayer->GetAccessLevel() < ADVOCATE_ACCESS)
|
|
break;
|
|
|
|
// Starts with string (was empty when I tested)
|
|
pReader->ReadString();
|
|
|
|
// Then position (target)
|
|
Position position;
|
|
position.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->Movement_Teleport(position);
|
|
break;
|
|
}
|
|
case ABUSE_LOG_REQUEST: // Send abuse report
|
|
{
|
|
// Read: string target, uint status = 1, string complaint
|
|
break;
|
|
}
|
|
case CHANNEL_ADD: // Join chat channel
|
|
{
|
|
// Read: uint channel ID
|
|
break;
|
|
}
|
|
case CHANNEL_REMOVE: // Leave chat channel
|
|
{
|
|
// Read: uint channel ID
|
|
break;
|
|
}
|
|
case CHANNEL_TEXT: // Channel Text
|
|
{
|
|
uint32_t channel_id = pReader->ReadUInt32();
|
|
char *msg = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (CheckForChatSpam())
|
|
{
|
|
std::string filteredText = FilterBadChatCharacters(msg);
|
|
ChannelText(channel_id, filteredText.c_str());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CHANNEL_LIST: // List of characters listening to a channel
|
|
{
|
|
// Read: PackableList<string> characters
|
|
break;
|
|
}
|
|
case CHANNEL_INDEX: // List of channels available to the player
|
|
{
|
|
// Read: PackableList<string> channels
|
|
break;
|
|
}
|
|
case NO_LONGER_VIEWING_CONTAINER: // No longer viewing contents
|
|
{
|
|
uint32_t container_id = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
NoLongerViewingContents(container_id);
|
|
break;
|
|
}
|
|
case ADD_ITEM_SHORTCUT: // Add item to shortcut bar
|
|
{
|
|
ShortCutData data;
|
|
data.UnPack(pReader);
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.AddShortCut(data);
|
|
break;
|
|
}
|
|
case REMOVE_ITEM_SHORTCUT: // Add item to shortcut bar
|
|
{
|
|
int index = pReader->Read<int>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.RemoveShortCut(index);
|
|
break;
|
|
}
|
|
case CHARACTER_OPTIONS: // Character Options
|
|
{
|
|
PlayerModule module;
|
|
if (!module.UnPack(pReader) || pReader->GetLastError())
|
|
break;
|
|
if (m_pPlayer && m_pPlayer->AsPlayer())
|
|
{
|
|
SendText("Updating character configuration.", LTT_SYSTEM_EVENT);
|
|
m_pPlayer->UpdateModuleFromClient(module);
|
|
}
|
|
else
|
|
DEBUG_DATA << "Playermodule call for invalid object";
|
|
break;
|
|
}
|
|
case CANCEL_ATTACK: // Cancel attack
|
|
{
|
|
// TODO
|
|
m_pPlayer->TryCancelAttack();
|
|
break;
|
|
}
|
|
case SPELLBOOK_REMOVE:
|
|
{
|
|
uint32_t spell_id = pReader->ReadUInt32();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (spell_id)
|
|
{
|
|
if (const CSpellBase *spell = MagicSystem::GetSpellTable()->GetSpellBase(spell_id))
|
|
{
|
|
if (m_pPlayer->m_Qualities._spell_book && m_pPlayer->m_Qualities._spell_book->Exists(spell_id))
|
|
{
|
|
m_pPlayer->m_Qualities.RemoveSpell(spell_id);
|
|
BinaryWriter removeSpellFromSpellbook;
|
|
removeSpellFromSpellbook.Write<uint32_t>(0x1A8);
|
|
removeSpellFromSpellbook.Write<uint32_t>(spell_id);
|
|
m_pPlayer->SendNetMessage(&removeSpellFromSpellbook, PRIVATE_MSG, TRUE, FALSE);
|
|
m_pPlayer->SendText(csprintf("You no longer know the %s spell.", spell->_name.c_str()), LTT_DEFAULT);
|
|
}
|
|
else
|
|
SendText("You don't know that spell!", LTT_DEFAULT);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QUERY_HEALTH: // Request health update
|
|
{
|
|
uint32_t target_id = pReader->ReadUInt32();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
RequestHealthUpdate(target_id);
|
|
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case QUERY_AGE:
|
|
{
|
|
uint32_t targetID = pReader->ReadUInt32(); // don't need this, query can't target anyone else
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
int age = m_pPlayer->m_Qualities.GetInt(AGE_INT, 0);
|
|
SendText(GetAgeString(age).c_str(), LTT_DEFAULT); // You have played for Xm Xd Xh Xm Xs.
|
|
break;
|
|
}
|
|
case QUERY_BIRTH:
|
|
{
|
|
uint32_t targetID = pReader->ReadUInt32(); // don't need this, query can't target anyone else
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
std::string DOB = m_pPlayer->InqStringQuality(DATE_OF_BIRTH_STRING, "");
|
|
SendText(csprintf("%s", DOB.c_str()), LTT_DEFAULT);
|
|
break;
|
|
}
|
|
case TEXT_INDIRECT: // Indirect Text (@me)
|
|
{
|
|
char *msg = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (CheckForChatSpam())
|
|
{
|
|
std::string filteredText = FilterBadChatCharacters(msg);
|
|
EmoteText(filteredText.c_str());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TEXT_EMOTE: // Emote Text (*laugh* sends 'laughs')
|
|
{
|
|
char *msg = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (CheckForChatSpam())
|
|
{
|
|
std::string filteredText = FilterBadChatCharacters(msg);
|
|
ActionText(filteredText.c_str());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ADD_TO_SPELLBAR: // Add item to spell bar
|
|
{
|
|
uint32_t spellID = pReader->Read<uint32_t>();
|
|
int index = pReader->Read<int>();
|
|
int spellBar = pReader->Read<int>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.AddSpellFavorite(spellID, index, spellBar);
|
|
break;
|
|
}
|
|
case REMOVE_FROM_SPELLBAR: // Remove item from spell bar
|
|
{
|
|
uint32_t spellID = pReader->Read<uint32_t>();
|
|
int spellBar = pReader->Read<int>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.RemoveSpellFavorite(spellID, spellBar);
|
|
break;
|
|
}
|
|
case PING: // Ping
|
|
{
|
|
Ping();
|
|
break;
|
|
}
|
|
case TRADE_OPEN: // Open Trade Negotiations
|
|
{
|
|
if (m_pPlayer->GetTradeManager() != NULL)
|
|
{
|
|
//already trading
|
|
return;
|
|
}
|
|
|
|
uint32_t target = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError())
|
|
return;
|
|
|
|
CWeenieObject *pOther = g_pWorld->FindWithinPVS(m_pPlayer, target);
|
|
CPlayerWeenie *pTarget;
|
|
if (pOther)
|
|
pTarget = pOther->AsPlayer();
|
|
|
|
if (!pOther || !pTarget)
|
|
{
|
|
// cannot open trade
|
|
m_pPlayer->SendText("Unable to open trade.", LTT_ERROR);
|
|
}
|
|
else if (pTarget->_playerModule.options_ & 0x20000)
|
|
{
|
|
SendText((pTarget->GetName() + " has disabled trading.").c_str(), LTT_ERROR);
|
|
}
|
|
else if (pTarget->IsBusyOrInAction())
|
|
{
|
|
SendText((pTarget->GetName() + " is busy.").c_str(), LTT_ERROR);
|
|
}
|
|
else if (pTarget->GetTradeManager() != NULL)
|
|
{
|
|
SendText((pTarget->GetName() + " is already trading with someone else!").c_str(), LTT_ERROR);
|
|
}
|
|
else
|
|
{
|
|
CTradeUseEvent *tradeEvent = new CTradeUseEvent;
|
|
tradeEvent->_target_id = pTarget->GetID();
|
|
tradeEvent->_max_use_distance = 1.0;
|
|
|
|
m_pPlayer->ExecuteUseEvent(tradeEvent);
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_CLOSE: // Close Trade Negotiations
|
|
{
|
|
TradeManager* tm = m_pPlayer->GetTradeManager();
|
|
if (tm)
|
|
{
|
|
tm->CloseTrade(m_pPlayer);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_ADD: // AddToTrade
|
|
{
|
|
TradeManager* tm = m_pPlayer->GetTradeManager();
|
|
if (tm)
|
|
{
|
|
uint32_t item = pReader->Read<uint32_t>();
|
|
|
|
tm->AddToTrade(m_pPlayer, item);
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_ACCEPT: // Accept trade
|
|
{
|
|
TradeManager* tm = m_pPlayer->GetTradeManager();
|
|
if (tm)
|
|
{
|
|
tm->AcceptTrade(m_pPlayer);
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_DECLINE: // Decline trade
|
|
{
|
|
TradeManager* tm = m_pPlayer->GetTradeManager();
|
|
if (tm)
|
|
{
|
|
tm->DeclineTrade(m_pPlayer);
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_RESET: // Reset trade
|
|
{
|
|
TradeManager* tm = m_pPlayer->GetTradeManager();
|
|
if (tm)
|
|
{
|
|
tm->ResetTrade(m_pPlayer);
|
|
}
|
|
break;
|
|
}
|
|
case CLEAR_PLAYER_CONSENT_LIST: // Clears the player's corpse looting consent list, /consent clear
|
|
{
|
|
m_pPlayer->ClearConsent(false);
|
|
break;
|
|
}
|
|
case DISPLAY_PLAYER_CONSENT_LIST: // Display the player's corpse looting consent list, /consent who
|
|
{
|
|
m_pPlayer->DisplayConsent();
|
|
break;
|
|
}
|
|
case REMOVE_FROM_PLAYER_CONSENT_LIST: // Remove your corpse looting permission for the given player, / consent remove
|
|
{
|
|
std::string targetName = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
CPlayerWeenie *target = g_pWorld->FindPlayer(targetName.c_str());
|
|
if (target)
|
|
{
|
|
m_pPlayer->RemoveConsent(target);
|
|
}
|
|
break;
|
|
}
|
|
case ADD_PLAYER_PERMISSION: // Grants a player corpse looting permission, /permit add
|
|
{
|
|
std::string targetName = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
CPlayerWeenie *target = g_pWorld->FindPlayer(targetName.c_str());
|
|
if (target && target->GetCharacterOptions() & AcceptLootPermits_CharacterOption)
|
|
{
|
|
m_pPlayer->AddCorpsePermission(target);
|
|
}
|
|
break;
|
|
}
|
|
case REMOVE_PLAYER_PERMISSION:
|
|
{
|
|
std::string targetName = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
CPlayerWeenie *target = g_pWorld->FindPlayer(targetName.c_str());
|
|
if (target && target->GetCharacterOptions() & AcceptLootPermits_CharacterOption)
|
|
{
|
|
m_pPlayer->RemoveCorpsePermission(target);
|
|
}
|
|
break;
|
|
}
|
|
case HOUSE_BUY: //House_BuyHouse
|
|
{
|
|
uint32_t slumlord = pReader->Read<uint32_t>();
|
|
|
|
// TODO sanity check on the number of items here
|
|
PackableList<uint32_t> items;
|
|
items.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseBuy(slumlord, items);
|
|
break;
|
|
}
|
|
case HOUSE_ABANDON: //House_AbandonHouse
|
|
{
|
|
HouseAbandon();
|
|
break;
|
|
}
|
|
case HOUSE_OF_PLAYER_QUERY: //House_QueryHouse
|
|
{
|
|
HouseRequestData();
|
|
break;
|
|
}
|
|
case HOUSE_RENT: //House_RentHouse
|
|
{
|
|
uint32_t slumlord = pReader->Read<uint32_t>();
|
|
|
|
// TODO sanity check on the number of items here
|
|
PackableList<uint32_t> items;
|
|
items.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
//HouseRent(slumlord, items);
|
|
break;
|
|
}
|
|
case DESIRED_COMPS_SET:
|
|
{
|
|
uint32_t compWCID = pReader->Read<uint32_t>();
|
|
uint32_t compQTY = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.AddOrUpdateDesiredComp(compWCID, compQTY);
|
|
break;
|
|
}
|
|
case HOUSE_ADD_GUEST: //House_AddPermanentGuest
|
|
{
|
|
std::string name = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseAddPersonToAccessList(name);
|
|
break;
|
|
}
|
|
case HOUSE_REMOVE_GUEST: //House_RemovePermanentGuest
|
|
{
|
|
std::string name = pReader->ReadString();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseRemovePersonFromAccessList(name);
|
|
break;
|
|
}
|
|
case HOUSE_SET_OPEN_ACCESS: //House_SetOpenHouseStatus
|
|
{
|
|
int newSetting = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseToggleOpenAccess(newSetting > 0);
|
|
break;
|
|
}
|
|
case HOUSE_CHANGE_STORAGE_PERMISSIONS: //House_ChangeStoragePermission
|
|
{
|
|
std::string name = pReader->ReadString();
|
|
int isAdd = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseAddOrRemovePersonToStorageList(name, isAdd > 0);
|
|
break;
|
|
}
|
|
case HOUSE_CLEAR_STORAGE_PERMISSIONS: //House_RemoveAllStoragePermission
|
|
{
|
|
HouseClearStorageAccess();
|
|
break;
|
|
}
|
|
case HOUSE_GUEST_LIST: //House_RequestFullGuestList
|
|
{
|
|
HouseRequestAccessList();
|
|
break;
|
|
}
|
|
case ALLEGIANCE_SET_MOTD:
|
|
{
|
|
MAllegianceMOTDSet_0254 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_QUERY_MOTD: //Request allegiance MOTD
|
|
{
|
|
MAllegianceMOTDQuery_0255 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CLEAR_MOTD:
|
|
{
|
|
MAllegianceMOTDClear_0256 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case HOUSE_QUERY_SLUMLORD:
|
|
{
|
|
// read in: uint32_t lord = pReader->ReadUInt32();
|
|
}
|
|
case HOUSE_SET_OPEN_STORAGE_ACCESS: //House_AddAllStoragePermission
|
|
{
|
|
HouseToggleOpenStorageAccess();
|
|
break;
|
|
}
|
|
case HOUSE_REMOVE_ALL_GUESTS: //House_RemoveAllPermanentGuests
|
|
{
|
|
HouseClearAccessList();
|
|
break;
|
|
}
|
|
case HOUSE_BOOT_ALL:
|
|
{
|
|
break;
|
|
}
|
|
case RECALL_HOUSE: // House Recall
|
|
{
|
|
HouseRecall();
|
|
break;
|
|
}
|
|
case ITEM_MANA_REQUEST: // Request Item Mana
|
|
{
|
|
uint32_t itemId = pReader->ReadUInt32();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->HandleItemManaRequest(itemId);
|
|
m_pPlayer->_nextHeartBeatEmote = Timer::cur_time + 30.0;
|
|
break;
|
|
}
|
|
case HOUSE_SET_HOOKS_VISIBILITY: // House_SetHooksVisibility
|
|
{
|
|
int newSetting = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseToggleHooks(newSetting > 0);
|
|
break;
|
|
}
|
|
case HOUSE_CHANGE_ALLEGIANCE_GUEST_PERMISSIONS: //House_ModifyAllegianceGuestPermission
|
|
{
|
|
int newSetting = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseAddOrRemoveAllegianceToAccessList(newSetting > 0);
|
|
break;
|
|
}
|
|
case HOUSE_CHANGE_ALLEGIANCE_STORAGE_PERMISSIONS: //House_ModifyAllegianceStoragePermission
|
|
{
|
|
int newSetting = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
HouseAddOrRemoveAllegianceToStorageList(newSetting > 0);
|
|
break;
|
|
}
|
|
case CHESS_JOIN: // Disabling until dev returns to update
|
|
{
|
|
//auto const guid = pReader->Read<uint32_t>();
|
|
//pReader->Read<uint32_t>(); // teamId, not used
|
|
|
|
//CWeenieObject* object = g_pWorld->FindObject(guid);
|
|
//if (!object)
|
|
// break;
|
|
|
|
//sChessManager->Join(m_pPlayer, object);
|
|
break;
|
|
}
|
|
case CHESS_QUIT: // Disabling until dev returns to update
|
|
//sChessManager->Quit(m_pPlayer);
|
|
break;
|
|
case CHESS_MOVE: // Disabling until dev returns to update
|
|
{
|
|
//GDLE::Chess::ChessPieceCoord from, to;
|
|
//from.UnPack(pReader);
|
|
//to.UnPack(pReader);
|
|
//sChessManager->Move(m_pPlayer, from, to);
|
|
break;
|
|
}
|
|
case CHESS_PASS: // Disabling until dev returns to update
|
|
//sChessManager->MovePass(m_pPlayer);
|
|
break;
|
|
case CHESS_STALEMATE: // Disabling until dev returns to update
|
|
{
|
|
//auto const on = pReader->Read<uint32_t>();
|
|
//sChessManager->Stalemate(m_pPlayer, on);
|
|
break;
|
|
}
|
|
case HOUSE_LIST_AVAILABLE:
|
|
{
|
|
// Read: HouseType houseType - type of house to list, cottage 1, villa 2, mansion 3, apartment 4
|
|
// Reply: 0x0271, HouseType houseType, PackableList<uint> houses, int numHouses
|
|
uint32_t houseType = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
GetHousesAvailable(houseType);
|
|
|
|
break;
|
|
}
|
|
case ALLEGIANCE_BOOT_PLAYER: // Boots a player from the allegiance, optionally all characters on their account
|
|
{
|
|
MAllegianceBoot_0277 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case RECALL_HOUSE_MANSION: // House_TeleToMansion
|
|
{
|
|
HouseMansionRecall();
|
|
break;
|
|
}
|
|
case DIE_COMMAND: // "/die" command
|
|
{
|
|
if (m_pPlayer->IsInPortalSpace())
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
break;
|
|
}
|
|
|
|
if (!m_pPlayer->IsDead() && !m_pPlayer->IsInPortalSpace() && m_pPlayer->_deathTimer < 0)
|
|
{
|
|
m_pPlayer->UpdatePKActivity();
|
|
m_pPlayer->_deathTimer = Timer::cur_time + 10.5;
|
|
m_pPlayer->_dieTextTimer = Timer::cur_time + 2.0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ALLEGIANCE_INFO_REQUEST: // allegiance info request
|
|
{
|
|
MAllegianceInfoRequest_027B msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case SPELLBOOK_FILTERS: // "/die" command
|
|
{
|
|
uint32_t filters = pReader->Read<uint32_t>();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
m_pPlayer->_playerModule.spell_filters_ = filters;
|
|
break;
|
|
}
|
|
case RECALL_MARKET: // Marketplace Recall
|
|
{
|
|
MarketplaceRecall();
|
|
break;
|
|
}
|
|
case PKLITE:
|
|
{
|
|
if (g_pConfig->PKOnly())
|
|
{
|
|
m_pPlayer->NotifyWeenieError(WERROR_PK_INVALID_STATUS);
|
|
m_pPlayer->SendText("You cannot be PKLite on this server", LTT_ERROR);
|
|
}
|
|
else
|
|
PKLiteEnable();
|
|
|
|
break;
|
|
}
|
|
case FELLOW_ASSIGN_NEW_LEADER: // "Fellowship Assign New Leader"
|
|
{
|
|
uint32_t target_id = pReader->Read<uint32_t>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipAssignNewLeader(target_id);
|
|
break;
|
|
}
|
|
case FELLOW_CHANGE_OPENNESS: // "Fellowship Change Openness"
|
|
{
|
|
int open = pReader->Read<int>();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
TryFellowshipChangeOpenness(open);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CHAT_BOOT:
|
|
{
|
|
MAllegianceChatBoot_02A0 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_ADD_PLAYER_BAN:
|
|
{
|
|
//MAllegianceBanAdd_02A1 msg(m_pPlayer);
|
|
//msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_REMOVE_PLAYER_BAN:
|
|
{
|
|
//MAllegianceBanRemove_02A2 msg(m_pPlayer);
|
|
//msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_LIST_BANS:
|
|
{
|
|
//MAllegianceBanList_02A3 msg(m_pPlayer);
|
|
//msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_REMOVE_OFFICER:
|
|
{
|
|
MAllegianceOfficerRemove_02A5 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_LIST_OFFICERS:
|
|
{
|
|
MAllegianceOfficersList_02A6 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case ALLEGIANCE_CLEAR_OFFICERS:
|
|
{
|
|
MAllegianceOfficersClear_02A7 msg(m_pPlayer);
|
|
msg.Parse(pReader);
|
|
break;
|
|
}
|
|
case RECALL_ALLEGIANCE_HOMETOWN: //Allegiance_RecallAllegianceHometown (bindstone)
|
|
{
|
|
AllegianceHometownRecall();
|
|
/*MAllegianceRecallHometown_02AB msg(m_pPlayer);
|
|
msg.Parse(pReader);*/
|
|
break;
|
|
}
|
|
case FINISH_BARBER:
|
|
{
|
|
//Read: DataID basePalette, DataID headObject, DataID headTexture, DataID defaultHeadTexture, DataID eyesTexture
|
|
// DataID defaultEyesTexture, DataID noseTexture, DataID defaultNoseTexture, DataID mouthTexture, DataID defaultMouthTexture
|
|
// DataID skinPalette, DataID hairPalette, DataID eyesPalette, DataID setupID, int option1, int option2
|
|
// option1 = toggle certain race options e.g. empyrean float or flaming head undead, option2 = not used?
|
|
|
|
uint32_t basePalette = pReader->Read<uint32_t>();
|
|
uint32_t headObject = pReader->Read<uint32_t>();
|
|
uint32_t headTexture = pReader->Read<uint32_t>();
|
|
uint32_t defaultHeadTexture = pReader->Read<uint32_t>();
|
|
uint32_t eyesTexture = pReader->Read<uint32_t>();
|
|
uint32_t defaultEyesTexture = pReader->Read<uint32_t>();
|
|
uint32_t noseTexture = pReader->Read<uint32_t>();
|
|
uint32_t defaultNoseTexture = pReader->Read<uint32_t>();
|
|
uint32_t mouthTexture = pReader->Read<uint32_t>();
|
|
uint32_t defaultMouthTexture = pReader->Read<uint32_t>();
|
|
uint32_t skinPalette = pReader->Read<uint32_t>();
|
|
uint32_t hairPalette = pReader->Read<uint32_t>();
|
|
uint32_t eyesPalette = pReader->Read<uint32_t>();
|
|
uint32_t setupID = pReader->Read<uint32_t>();
|
|
int option1 = pReader->Read<int>();
|
|
int option2 = pReader->Read<int>();
|
|
|
|
m_pPlayer->m_Qualities.SetDataID(HEAD_OBJECT_DID, headObject);
|
|
m_pPlayer->m_Qualities.SetDataID(EYES_TEXTURE_DID, eyesTexture);
|
|
m_pPlayer->m_Qualities.SetDataID(NOSE_TEXTURE_DID, noseTexture);
|
|
m_pPlayer->m_Qualities.SetDataID(MOUTH_TEXTURE_DID, mouthTexture);
|
|
m_pPlayer->m_Qualities.SetDataID(SKIN_PALETTE_DID, skinPalette);
|
|
m_pPlayer->m_Qualities.SetDataID(HAIR_PALETTE_DID, hairPalette);
|
|
m_pPlayer->m_Qualities.SetDataID(EYES_PALETTE_DID, eyesPalette);
|
|
m_pPlayer->m_Qualities.SetDataID(SETUP_DID, setupID);
|
|
|
|
if (m_pPlayer->m_Qualities.GetInt(HERITAGE_GROUP_INT, 0) == Empyrean_HeritageGroup && option1 == 0)
|
|
{
|
|
m_pPlayer->m_Qualities.SetDataID(MOTION_TABLE_DID, 0x9000207);
|
|
m_pPlayer->NotifyDIDStatUpdated(MOTION_TABLE_DID);
|
|
}
|
|
else if (m_pPlayer->m_Qualities.GetInt(HERITAGE_GROUP_INT, 0) == Empyrean_HeritageGroup && option1 == 1)
|
|
{
|
|
m_pPlayer->m_Qualities.SetDataID(MOTION_TABLE_DID, 0x900020D);
|
|
m_pPlayer->NotifyDIDStatUpdated(MOTION_TABLE_DID);
|
|
}
|
|
|
|
m_pPlayer->UpdateModel();
|
|
break;
|
|
}
|
|
case MOVEMENT_JUMP: // Jump Movement
|
|
{
|
|
float extent = pReader->Read<float>(); // extent
|
|
|
|
Vector jumpVelocity;
|
|
jumpVelocity.UnPack(pReader);
|
|
|
|
Position position;
|
|
position.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
// CTransition *transition = m_pPlayer->transition(&m_pPlayer->m_Position, &position, 0);
|
|
|
|
/*
|
|
CTransition *transit = m_pPlayer->transition(&m_pPlayer->m_Position, &position, 0);
|
|
if (transit)
|
|
{
|
|
m_pPlayer->SetPositionInternal(transit);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
double dist = m_pPlayer->m_Position.distance(position);
|
|
if (dist >= 5)
|
|
{
|
|
m_pPlayer->_force_position_timestamp++;
|
|
m_pPlayer->Movement_UpdatePos();
|
|
|
|
m_pPlayer->SendText(csprintf("Correcting position due to jump %f", dist), LTT_DEFAULT);
|
|
}
|
|
*/
|
|
|
|
double dist = m_pPlayer->m_Position.distance(position);
|
|
if (dist >= 10)
|
|
{
|
|
// Snap them back to their previous position
|
|
m_pPlayer->_force_position_timestamp++;
|
|
}
|
|
else
|
|
{
|
|
//m_pPlayer->SetPositionSimple(&position, TRUE);
|
|
|
|
|
|
CTransition *transit = m_pPlayer->transition(&m_pPlayer->m_Position, &position, 0);
|
|
if (transit)
|
|
{
|
|
m_pPlayer->SetPositionInternal(transit);
|
|
}
|
|
|
|
}
|
|
|
|
// m_pPlayer->m_Position = position;
|
|
|
|
/*
|
|
int32_t stamina = 0;
|
|
m_pPlayer->get_minterp()->jump(extent, stamina);
|
|
if (stamina > 0)
|
|
{
|
|
unsigned int curStamina = m_pPlayer->GetStamina();
|
|
|
|
if (stamina > curStamina)
|
|
{
|
|
// should maybe change the extent? idk
|
|
m_pPlayer->SetStamina(0);
|
|
}
|
|
else
|
|
m_pPlayer->SetStamina(curStamina - stamina);
|
|
}
|
|
|
|
m_pPlayer->Animation_Jump(extent, jumpVelocity);
|
|
m_pPlayer->m_bAnimUpdate = TRUE;
|
|
*/
|
|
int32_t stamina = 0;
|
|
if (m_pPlayer->JumpStaminaCost(extent, stamina) && stamina > 0)
|
|
{
|
|
unsigned int curStamina = m_pPlayer->GetStamina();
|
|
|
|
if (stamina > curStamina)
|
|
{
|
|
// should maybe change the extent? idk
|
|
m_pPlayer->SetStamina(0);
|
|
}
|
|
else
|
|
m_pPlayer->SetStamina(curStamina - stamina);
|
|
}
|
|
|
|
m_pPlayer->transient_state &= ~((uint32_t)TransientState::CONTACT_TS);
|
|
m_pPlayer->transient_state &= ~((uint32_t)WATER_CONTACT_TS);
|
|
m_pPlayer->calc_acceleration();
|
|
m_pPlayer->set_on_walkable(FALSE);
|
|
m_pPlayer->set_local_velocity(jumpVelocity, 0);
|
|
|
|
/*
|
|
Vector localVel = m_pPlayer->get_local_physics_velocity();
|
|
Vector vel = m_pPlayer->m_velocityVector;
|
|
m_pPlayer->EmoteLocal(csprintf("Received jump with velocity %.1f %.1f %.1f (my lv: %.1f %.1f %.1f my v: %.1f %.1f %.1f)",
|
|
jumpVelocity.x, jumpVelocity.y, jumpVelocity.z,
|
|
localVel.x, localVel.y, localVel.z,
|
|
vel.x, vel.y, vel.z));
|
|
*/
|
|
|
|
m_pPlayer->Movement_UpdateVector();
|
|
|
|
break;
|
|
}
|
|
case MOVEMENT_MOVE_TO_STATE: // CM_Movement__Event_MoveToState (update vector movement?)
|
|
{
|
|
if (m_pPlayer->m_bChangingStance)
|
|
{
|
|
//DEBUG_DATA << "Player changing stance during 0xF61C. Ignoring.";
|
|
break;
|
|
}
|
|
|
|
MoveToStatePack moveToState;
|
|
moveToState.UnPack(pReader);
|
|
|
|
if (pReader->GetLastError())
|
|
{
|
|
SERVER_WARN << "Bad animation message!";
|
|
SERVER_WARN << pReader->GetDataStart() << pReader->GetDataLen();
|
|
break;
|
|
}
|
|
|
|
//if (is_newer_event_stamp(moveToState.server_control_timestamp, m_pPlayer->_server_control_timestamp))
|
|
//{
|
|
// LOG(Temp, Normal, "Old server control timestamp on 0xF61C. Ignoring.\n");
|
|
// break;
|
|
//}
|
|
|
|
if (is_newer_event_stamp(moveToState.teleport_timestamp, m_pPlayer->_teleport_timestamp))
|
|
{
|
|
//SERVER_WARN << "Old teleport timestamp on 0xF61C. Ignoring.";
|
|
break;
|
|
}
|
|
if (is_newer_event_stamp(moveToState.force_position_ts, m_pPlayer->_force_position_timestamp))
|
|
{
|
|
//SERVER_WARN << "Old force position timestamp on 0xF61C. Ignoring.";
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
if (m_pPlayer->IsDead())
|
|
{
|
|
SERVER_WARN << "Dead players can't move. Ignoring.";
|
|
break;
|
|
}
|
|
|
|
/*
|
|
CTransition *transit = m_pPlayer->transition(&m_pPlayer->m_Position, &moveToState.position, 0);
|
|
if (transit)
|
|
{
|
|
m_pPlayer->SetPositionInternal(transit);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
double dist = m_pPlayer->m_Position.distance(moveToState.position);
|
|
if (dist >= 5)
|
|
{
|
|
m_pPlayer->_force_position_timestamp++;
|
|
m_pPlayer->Movement_UpdatePos();
|
|
|
|
m_pPlayer->SendText(csprintf("Correcting position due to state %f", dist), LTT_DEFAULT);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
bool bHasCell = m_pPlayer->cell ? true : false;
|
|
m_pPlayer->SetPositionSimple(&moveToState.position, TRUE);
|
|
if (!m_pPlayer->cell && bHasCell)
|
|
{
|
|
m_pPlayer->SendText("Damnet...", LTT_DEFAULT);
|
|
}
|
|
*/
|
|
|
|
double dist = m_pPlayer->m_Position.distance(moveToState.position);
|
|
if (dist >= 10)
|
|
{
|
|
// Snap them back to their previous position
|
|
m_pPlayer->_force_position_timestamp++;
|
|
}
|
|
else
|
|
{
|
|
//m_pPlayer->SetPositionSimple(&moveToState.position, TRUE);
|
|
|
|
|
|
CTransition *transit = m_pPlayer->transition(&m_pPlayer->m_Position, &moveToState.position, 0);
|
|
if (transit)
|
|
{
|
|
m_pPlayer->SetPositionInternal(transit);
|
|
}
|
|
|
|
}
|
|
|
|
// m_pPlayer->m_Position = moveToState.position; // should interpolate to this, but oh well
|
|
|
|
/*
|
|
if (moveToState.contact)
|
|
{
|
|
m_pPlayer->transient_state |= ((uint32_t)TransientState::CONTACT_TS);
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->transient_state &= ~((uint32_t)TransientState::CONTACT_TS);
|
|
m_pPlayer->transient_state &= ~((uint32_t)WATER_CONTACT_TS);
|
|
}
|
|
m_pPlayer->calc_acceleration();
|
|
m_pPlayer->set_on_walkable(moveToState.contact);
|
|
|
|
*/
|
|
|
|
m_pPlayer->get_minterp()->standing_longjump = moveToState.longjump_mode ? TRUE : FALSE;
|
|
m_pPlayer->last_move_was_autonomous = true;
|
|
m_pPlayer->cancel_moveto();
|
|
|
|
if (m_pPlayer->m_UseManager && m_pPlayer->m_UseManager->IsMoving())
|
|
m_pPlayer->m_UseManager->HandleMoveToDone(WERROR_INTERRUPTED);
|
|
|
|
if (!(moveToState.raw_motion_state.current_style & CM_Style) && moveToState.raw_motion_state.current_style)
|
|
{
|
|
SERVER_WARN << "Bad style received" << moveToState.raw_motion_state.current_style;
|
|
break;
|
|
}
|
|
|
|
if (moveToState.raw_motion_state.forward_command & CM_Action)
|
|
{
|
|
SERVER_WARN << "Bad forward command received" << moveToState.raw_motion_state.forward_command;
|
|
break;
|
|
}
|
|
|
|
if (moveToState.raw_motion_state.sidestep_command & CM_Action)
|
|
{
|
|
SERVER_WARN << "Bad sidestep command received" << moveToState.raw_motion_state.sidestep_command;
|
|
break;
|
|
}
|
|
|
|
if (moveToState.raw_motion_state.turn_command & CM_Action)
|
|
{
|
|
SERVER_WARN << "Bad turn command received" << moveToState.raw_motion_state.turn_command;
|
|
break;
|
|
}
|
|
|
|
CMotionInterp *minterp = m_pPlayer->get_minterp();
|
|
minterp->raw_state = moveToState.raw_motion_state;
|
|
minterp->apply_raw_movement(TRUE, minterp->motion_allows_jump(minterp->interpreted_state.forward_command != 0));
|
|
|
|
WORD newestActionStamp = m_MoveActionStamp;
|
|
|
|
// Cancel attack
|
|
if (((m_pPlayer->m_bCancelAttack && minterp->interpreted_state.forward_speed < 0) || (m_pPlayer->m_bCancelAttackDuplicate && minterp->interpreted_state.forward_speed < 0)) && !moveToState.longjump_mode)
|
|
{
|
|
// Client sends multiple movements with negative speed (backsteps) even after Cancel Attacks have been handled. This catches those extras.
|
|
if (!m_pPlayer->m_bCancelAttackDuplicate)
|
|
m_pPlayer->m_bCancelAttackDuplicate = true;
|
|
else if (!m_pPlayer->m_bCancelAttack)
|
|
m_pPlayer->m_bCancelAttackDuplicate = false;
|
|
|
|
m_pPlayer->DoForcedStopCompletely();
|
|
m_pPlayer->m_bCancelAttack = false;
|
|
}
|
|
|
|
for (const auto &actionNew : moveToState.raw_motion_state.actions)
|
|
{
|
|
if (m_pPlayer->get_minterp()->interpreted_state.GetNumActions() >= MAX_EMOTE_QUEUE)
|
|
break;
|
|
|
|
if (is_newer_event_stamp(newestActionStamp, actionNew.stamp))
|
|
{
|
|
uint32_t commandID = GetCommandID(actionNew.action);
|
|
|
|
if (!(commandID & CM_Action) || !(commandID & CM_ChatEmote))
|
|
{
|
|
SERVER_WARN << "Bad action received" << commandID;
|
|
continue;
|
|
}
|
|
|
|
MovementParameters params;
|
|
params.action_stamp = ++m_pPlayer->m_wAnimSequence;
|
|
params.autonomous = 1;
|
|
if (actionNew.speed < -1)
|
|
params.speed = -1;
|
|
else if (actionNew.speed > 1)
|
|
params.speed = 1;
|
|
else
|
|
params.speed = actionNew.speed;
|
|
m_pPlayer->get_minterp()->DoMotion(commandID, ¶ms);
|
|
|
|
// minterp->interpreted_state.AddAction(ActionNode(actionNew.action, actionNew.speed, ++m_pPlayer->m_wAnimSequence, TRUE));
|
|
|
|
// newestActionStamp = actionNew.stamp;
|
|
// m_pPlayer->Animation_PlayEmote(actionNew.action, actionNew.speed);
|
|
}
|
|
}
|
|
|
|
m_MoveActionStamp = newestActionStamp;
|
|
|
|
// m_pPlayer->Movement_UpdatePos();
|
|
m_pPlayer->Animation_Update();
|
|
// m_pPlayer->m_bAnimUpdate = TRUE;
|
|
|
|
//m_pPlayer->Movement_UpdatePos();
|
|
break;
|
|
}
|
|
case MOVEMENT_DO_MOVEMENT_COMMAND:
|
|
{
|
|
// Read: uint motion, float speed, uint holdKey
|
|
break;
|
|
}
|
|
case MOVEMENT_TURN_TO:
|
|
{
|
|
// Read: TurnToEventPack ttep - set of turning data, not in client
|
|
break;
|
|
}
|
|
case MOVEMENT_STOP:
|
|
{
|
|
// Read: uint motion, uint holdKey
|
|
break;
|
|
}
|
|
case MOVEMENT_AUTONOMY_LEVEL:
|
|
{
|
|
// Read: uint autonomyLevel - Seems to be 0, 1 or 2. I think 0/1 is server controlled, 2 is client controlled
|
|
// Align to uint32_t boundary
|
|
break;
|
|
}
|
|
case MOVEMENT_AUTONOMOUS_POSITION: // Update Exact Position
|
|
{
|
|
Position position;
|
|
position.UnPack(pReader);
|
|
|
|
WORD instance = pReader->ReadUInt16();
|
|
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
if (instance != m_pPlayer->_instance_timestamp)
|
|
{
|
|
SERVER_WARN << "Bad instance.";
|
|
break;
|
|
}
|
|
|
|
WORD server_control_timestamp = pReader->ReadUInt16();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
//if (is_newer_event_stamp(server_control_timestamp, m_pPlayer->_server_control_timestamp))
|
|
//{
|
|
// LOG(Temp, Normal, "Old server control timestamp. Ignoring.\n");
|
|
// break;
|
|
//}
|
|
|
|
WORD teleport_timestamp = pReader->ReadUInt16();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
if (is_newer_event_stamp(teleport_timestamp, m_pPlayer->_teleport_timestamp))
|
|
{
|
|
//SERVER_WARN << "Old teleport timestamp. Ignoring.";
|
|
break;
|
|
}
|
|
|
|
WORD force_position_ts = pReader->ReadUInt16();
|
|
if (pReader->GetLastError())
|
|
break;
|
|
if (is_newer_event_stamp(force_position_ts, m_pPlayer->_force_position_timestamp))
|
|
{
|
|
//SERVER_WARN << "Old force position timestamp. Ignoring.";
|
|
break;
|
|
}
|
|
|
|
BOOL bHasContact = pReader->ReadBYTE() ? TRUE : FALSE;
|
|
if (pReader->GetLastError())
|
|
break;
|
|
|
|
double dist = m_pPlayer->m_Position.distance(position);
|
|
if (dist >= 10)
|
|
{
|
|
// Snap them back to their previous position
|
|
m_pPlayer->_force_position_timestamp++;
|
|
// m_pPlayer->SendText(csprintf("Correcting position due to position update %f", dist), LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
CTransition *transit = m_pPlayer->transition(&m_pPlayer->m_Position, &position, 0);
|
|
if (transit)
|
|
{
|
|
m_pPlayer->SetPositionInternal(transit);
|
|
*/
|
|
/*
|
|
double distFromClient = m_pPlayer->m_Position.distance(position);
|
|
|
|
if (distFromClient >= 3.0)
|
|
{
|
|
m_pPlayer->_force_position_timestamp++;
|
|
}
|
|
}
|
|
*/
|
|
|
|
m_pPlayer->SetPositionSimple(&position, TRUE);
|
|
|
|
/*
|
|
if (!m_pPlayer->cell && bHasCell)
|
|
{
|
|
m_pPlayer->SendText("Damnet...", LTT_DEFAULT);
|
|
}
|
|
*/
|
|
// m_pPlayer->m_Position = position; // should interpolate this, not set this directly, but oh well
|
|
|
|
}
|
|
|
|
if (bHasContact)
|
|
{
|
|
m_pPlayer->transient_state |= ((uint32_t)TransientState::CONTACT_TS);
|
|
}
|
|
else
|
|
{
|
|
m_pPlayer->transient_state &= ~((uint32_t)TransientState::CONTACT_TS);
|
|
m_pPlayer->transient_state &= ~((uint32_t)WATER_CONTACT_TS);
|
|
}
|
|
m_pPlayer->calc_acceleration();
|
|
m_pPlayer->set_on_walkable(bHasContact);
|
|
|
|
m_pPlayer->Movement_UpdatePos();
|
|
break;
|
|
}
|
|
case MOVEMENT_JUMP_NON_AUTONOMOUS:
|
|
{
|
|
// Read: float extent - Power of jump
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
//Unknown Event
|
|
#ifdef _DEBUG
|
|
SERVER_WARN << "Unhandled client event" << dwEvent;
|
|
#endif
|
|
// LOG_BYTES(Temp, Verbose, in->GetDataPtr(), in->GetDataEnd() - in->GetDataPtr() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|