zaela-Server/zone/corpse.cpp

1632 lines
47 KiB
C++
Raw Permalink Normal View History

2013-05-09 10:44:08 -04:00
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net)
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
2013-02-16 16:14:39 -08:00
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
2013-05-09 10:44:08 -04:00
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2013-02-16 16:14:39 -08:00
*/
/*
New class for handeling corpses and everything associated with them.
Child of the Mob class.
-Quagmire
*/
2013-02-16 16:14:39 -08:00
#ifdef _WINDOWS
#if (!defined(_MSC_VER) || (defined(_MSC_VER) && _MSC_VER < 1900))
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
2013-02-16 16:14:39 -08:00
#endif
#include "../common/global_define.h"
#include "../common/eqemu_logsys.h"
#include "../common/rulesys.h"
2014-08-21 19:33:02 -07:00
#include "../common/string_util.h"
#include "../common/say_link.h"
#include "corpse.h"
#include "entity.h"
#include "groups.h"
#include "mob.h"
#include "raids.h"
#ifdef BOTS
#include "bot.h"
#endif
#include "quest_parser_collection.h"
2014-08-21 23:46:01 -07:00
#include "string_ids.h"
2013-02-16 16:14:39 -08:00
#include "worldserver.h"
#include <iostream>
2013-02-16 16:14:39 -08:00
extern EntityList entity_list;
extern Zone* zone;
extern WorldServer worldserver;
extern npcDecayTimes_Struct npcCorpseDecayTimes[100];
void Corpse::SendEndLootErrorPacket(Client* client) {
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_LootComplete, 0);
2013-02-16 16:14:39 -08:00
client->QueuePacket(outapp);
safe_delete(outapp);
}
void Corpse::SendLootReqErrorPacket(Client* client, LootResponse response) {
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct));
2013-02-16 16:14:39 -08:00
moneyOnCorpseStruct* d = (moneyOnCorpseStruct*) outapp->pBuffer;
d->response = static_cast<uint8>(response);
2013-02-16 16:14:39 -08:00
d->unknown1 = 0x5a;
d->unknown2 = 0x40;
client->QueuePacket(outapp);
safe_delete(outapp);
}
Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std::string in_charname, const glm::vec4& position, std::string time_of_death, bool rezzed, bool was_at_graveyard, uint32 guild_consent_id) {
2014-11-23 15:55:38 -06:00
uint32 item_count = database.GetCharacterCorpseItemCount(in_dbid);
2016-05-25 16:10:28 -04:00
auto buffer =
new char[sizeof(PlayerCorpse_Struct) + (item_count * sizeof(player_lootitem::ServerLootItem_Struct))];
2014-11-23 15:55:38 -06:00
PlayerCorpse_Struct *pcs = (PlayerCorpse_Struct*)buffer;
database.LoadCharacterCorpseData(in_dbid, pcs);
2014-11-23 01:04:50 -06:00
/* Load Items */
ItemList itemlist;
ServerLootItem_Struct* tmp = nullptr;
2014-11-23 15:55:38 -06:00
for (unsigned int i = 0; i < pcs->itemcount; i++) {
tmp = new ServerLootItem_Struct;
2014-11-23 15:55:38 -06:00
memcpy(tmp, &pcs->items[i], sizeof(player_lootitem::ServerLootItem_Struct));
itemlist.push_back(tmp);
}
/* Create Corpse Entity */
2016-05-25 16:10:28 -04:00
auto pc = new Corpse(in_dbid, // uint32 in_dbid
in_charid, // uint32 in_charid
in_charname.c_str(), // char* in_charname
&itemlist, // ItemList* in_itemlist
pcs->copper, // uint32 in_copper
pcs->silver, // uint32 in_silver
pcs->gold, // uint32 in_gold
pcs->plat, // uint32 in_plat
position,
pcs->size, // float in_size
pcs->gender, // uint8 in_gender
pcs->race, // uint16 in_race
pcs->class_, // uint8 in_class
pcs->deity, // uint8 in_deity
pcs->level, // uint8 in_level
pcs->texture, // uint8 in_texture
pcs->helmtexture, // uint8 in_helmtexture
pcs->exp, // uint32 in_rezexp
was_at_graveyard // bool wasAtGraveyard
);
2014-12-21 13:45:26 -08:00
2014-11-23 15:55:38 -06:00
if (pcs->locked)
pc->Lock();
/* Load Item Tints */
2016-06-01 04:58:52 -04:00
pc->item_tint.Head.Color = pcs->item_tint.Head.Color;
pc->item_tint.Chest.Color = pcs->item_tint.Chest.Color;
pc->item_tint.Arms.Color = pcs->item_tint.Arms.Color;
pc->item_tint.Wrist.Color = pcs->item_tint.Wrist.Color;
pc->item_tint.Hands.Color = pcs->item_tint.Hands.Color;
pc->item_tint.Legs.Color = pcs->item_tint.Legs.Color;
pc->item_tint.Feet.Color = pcs->item_tint.Feet.Color;
pc->item_tint.Primary.Color = pcs->item_tint.Primary.Color;
pc->item_tint.Secondary.Color = pcs->item_tint.Secondary.Color;
2014-11-23 15:55:38 -06:00
/* Load Physical Appearance */
2014-11-23 15:55:38 -06:00
pc->haircolor = pcs->haircolor;
pc->beardcolor = pcs->beardcolor;
pc->eyecolor1 = pcs->eyecolor1;
pc->eyecolor2 = pcs->eyecolor2;
pc->hairstyle = pcs->hairstyle;
pc->luclinface = pcs->face;
pc->beard = pcs->beard;
pc->drakkin_heritage = pcs->drakkin_heritage;
pc->drakkin_tattoo = pcs->drakkin_tattoo;
pc->drakkin_details = pcs->drakkin_details;
pc->IsRezzed(rezzed);
pc->become_npc = false;
pc->consented_guild_id = guild_consent_id;
pc->UpdateEquipmentLight(); // itemlist populated above..need to determine actual values
2014-11-23 15:55:38 -06:00
safe_delete_array(pcs);
return pc;
2013-02-16 16:14:39 -08:00
}
Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime)
// vesuvias - appearence fix
2013-05-09 10:44:08 -04:00
: Mob("Unnamed_Corpse","",0,0,in_npc->GetGender(),in_npc->GetRace(),in_npc->GetClass(),BT_Humanoid,//bodytype added
in_npc->GetDeity(),in_npc->GetLevel(),in_npc->GetNPCTypeID(),in_npc->GetSize(),0,
in_npc->GetPosition(), in_npc->GetInnateLightType(), in_npc->GetTexture(),in_npc->GetHelmTexture(),
2013-05-09 10:44:08 -04:00
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,EQ::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
(*in_npctypedata)->use_model, false),
2013-05-09 10:44:08 -04:00
corpse_decay_timer(in_decaytime),
corpse_rez_timer(0),
2013-02-16 16:14:39 -08:00
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(0),
loot_cooldown_timer(10)
{
corpse_graveyard_timer.Disable();
is_corpse_changed = false;
is_player_corpse = false;
is_locked = false;
being_looted_by = 0xFFFFFFFF;
2013-02-16 16:14:39 -08:00
if (in_itemlist) {
itemlist = *in_itemlist;
in_itemlist->clear();
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
SetCash(in_npc->GetCopper(), in_npc->GetSilver(), in_npc->GetGold(), in_npc->GetPlatinum());
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
npctype_id = in_npctypeid;
SetPlayerKillItemID(0);
char_id = 0;
corpse_db_id = 0;
player_corpse_depop = false;
strcpy(corpse_name, in_npc->GetName());
2013-02-16 16:14:39 -08:00
strcpy(name, in_npc->GetName());
2014-12-21 13:45:26 -08:00
2013-02-16 16:14:39 -08:00
for(int count = 0; count < 100; count++) {
if ((level >= npcCorpseDecayTimes[count].minlvl) && (level <= npcCorpseDecayTimes[count].maxlvl)) {
corpse_decay_timer.SetTimer(npcCorpseDecayTimes[count].seconds*1000);
break;
}
}
if(IsEmpty()) {
2013-02-16 16:14:39 -08:00
corpse_decay_timer.SetTimer(RuleI(NPC,EmptyNPCCorpseDecayTimeMS)+1000);
}
2014-12-05 20:16:13 -08:00
if(in_npc->HasPrivateCorpse()) {
2013-02-16 16:14:39 -08:00
corpse_delay_timer.SetTimer(corpse_decay_timer.GetRemainingTime() + 1000);
}
for (int i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0;
}
this->rez_experience = 0;
UpdateEquipmentLight();
UpdateActiveLight();
2018-08-13 22:32:36 -04:00
loot_request_type = LootRequestType::Forbidden;
2013-02-16 16:14:39 -08:00
}
2014-11-22 23:28:20 -06:00
Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
"Unnamed_Corpse", // const char* in_name,
"", // const char* in_lastname,
0, // int32 in_cur_hp,
0, // int32 in_max_hp,
client->GetGender(), // uint8 in_gender,
client->GetRace(), // uint16 in_race,
client->GetClass(), // uint8 in_class,
BT_Humanoid, // bodyType in_bodytype,
client->GetDeity(), // uint8 in_deity,
client->GetLevel(), // uint8 in_level,
0, // uint32 in_npctype_id,
client->GetSize(), // float in_size,
0, // float in_runspeed,
client->GetPosition(),
client->GetInnateLightType(), // uint8 in_light, - verified for client innate_light value
2014-11-22 23:28:20 -06:00
client->GetTexture(), // uint8 in_texture,
client->GetHelmTexture(), // uint8 in_helmtexture,
0, // uint16 in_ac,
0, // uint16 in_atk,
0, // uint16 in_str,
0, // uint16 in_sta,
0, // uint16 in_dex,
0, // uint16 in_agi,
0, // uint16 in_int,
0, // uint16 in_wis,
0, // uint16 in_cha,
client->GetPP().haircolor, // uint8 in_haircolor,
client->GetPP().beardcolor, // uint8 in_beardcolor,
client->GetPP().eyecolor1, // uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye?
client->GetPP().eyecolor2, // uint8 in_eyecolor2,
client->GetPP().hairstyle, // uint8 in_hairstyle,
client->GetPP().face, // uint8 in_luclinface,
client->GetPP().beard, // uint8 in_beard,
client->GetPP().drakkin_heritage, // uint32 in_drakkin_heritage,
client->GetPP().drakkin_tattoo, // uint32 in_drakkin_tattoo,
client->GetPP().drakkin_details, // uint32 in_drakkin_details,
EQ::TintProfile(), // uint32 in_armor_tint[_MaterialCount],
2014-11-22 23:28:20 -06:00
0xff, // uint8 in_aa_title,
0, // uint8 in_see_invis, // see through invis
0, // uint8 in_see_invis_undead, // see through invis vs. undead
0, // uint8 in_see_hide,
0, // uint8 in_see_improved_hide,
0, // int32 in_hp_regen,
0, // int32 in_mana_regen,
0, // uint8 in_qglobal,
0, // uint8 in_maxlevel,
0, // uint32 in_scalerate
0, // uint8 in_armtexture,
0, // uint8 in_bracertexture,
0, // uint8 in_handtexture,
0, // uint8 in_legtexture,
0, // uint8 in_feettexture,
0, // uint8 in_usemodel,
0 // bool in_always_aggro
),
2013-02-16 16:14:39 -08:00
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
2013-02-16 16:14:39 -08:00
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)),
loot_cooldown_timer(10)
2013-02-16 16:14:39 -08:00
{
int i;
2013-02-16 16:14:39 -08:00
PlayerProfile_Struct *pp = &client->GetPP();
EQ::ItemInstance *item = nullptr;
2013-02-16 16:14:39 -08:00
/* Check if Zone has Graveyard First */
2013-02-16 16:14:39 -08:00
if(!zone->HasGraveyard()) {
corpse_graveyard_timer.Disable();
}
for (i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0;
}
2013-02-16 16:14:39 -08:00
2020-01-29 18:34:33 -05:00
if (client->AutoConsentGroupEnabled()) {
Group* grp = client->GetGroup();
consented_group_id = grp ? grp->GetID() : 0;
2020-01-29 18:34:33 -05:00
}
if (client->AutoConsentRaidEnabled()) {
Raid* raid = client->GetRaid();
consented_raid_id = raid ? raid->GetID() : 0;
2020-01-29 18:34:33 -05:00
}
consented_guild_id = client->AutoConsentGuildEnabled() ? client->GuildID() : 0;
is_corpse_changed = true;
rez_experience = in_rezexp;
can_corpse_be_rezzed = true;
is_player_corpse = true;
is_locked = false;
being_looted_by = 0xFFFFFFFF;
char_id = client->CharacterID();
2014-11-22 23:28:20 -06:00
corpse_db_id = 0;
player_corpse_depop = false;
2013-02-16 16:14:39 -08:00
copper = 0;
silver = 0;
gold = 0;
platinum = 0;
strcpy(corpse_name, pp->name);
strcpy(name, pp->name);
2013-05-06 13:07:41 -04:00
/* become_npc was not being initialized which led to some pretty funky things with newly created corpses */
2013-02-16 16:14:39 -08:00
become_npc = false;
SetPlayerKillItemID(0);
2013-02-16 16:14:39 -08:00
/* Check Rule to see if we can leave corpses */
if(!RuleB(Character, LeaveNakedCorpses) ||
RuleB(Character, LeaveCorpses) &&
2014-11-22 23:28:20 -06:00
GetLevel() >= RuleI(Character, DeathItemLossLevel)) {
2013-02-16 16:14:39 -08:00
// cash
2013-05-09 10:44:08 -04:00
// Let's not move the cash when 'RespawnFromHover = true' && 'client->GetClientVersion() < EQClientSoF' since the client doesn't.
// (change to first client that supports 'death hover' mode, if not SoF.)
if (!RuleB(Character, RespawnFromHover) || client->ClientVersion() < EQ::versions::ClientVersion::SoF) {
2013-05-09 10:44:08 -04:00
SetCash(pp->copper, pp->silver, pp->gold, pp->platinum);
pp->copper = 0;
pp->silver = 0;
pp->gold = 0;
pp->platinum = 0;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// get their tints
2016-06-01 04:58:52 -04:00
memcpy(&item_tint.Slot, &client->GetPP().item_tint, sizeof(item_tint));
2013-05-06 13:07:41 -04:00
2015-01-31 17:03:44 -05:00
// TODO soulbound items need not be added to corpse, but they need
2013-02-16 16:14:39 -08:00
// to go into the regular slots on the player, out of bags
std::list<uint32> removed_list;
2018-08-13 22:32:36 -04:00
// ideally, we would start at invslot::slotGeneral1 and progress to invslot::slotCursor..
// ..then regress and process invslot::EQUIPMENT_BEGIN through invslot::EQUIPMENT_END...
// without additional work to database loading of player corpses, this order is not
// currently preserved and a re-work of this processing loop is not warranted.
for (i = EQ::invslot::POSSESSIONS_BEGIN; i <= EQ::invslot::POSSESSIONS_END; ++i) {
2013-02-16 16:14:39 -08:00
item = client->GetInv().GetItem(i);
if (item == nullptr) { continue; }
2013-02-16 16:14:39 -08:00
if(!client->IsBecomeNPC() || (client->IsBecomeNPC() && !item->GetItem()->NoRent))
MoveItemToCorpse(client, item, i, removed_list);
2013-02-16 16:14:39 -08:00
}
2013-05-09 10:44:08 -04:00
database.TransactionBegin();
2018-08-13 22:32:36 -04:00
// this should not be modified to include the entire range of invtype::TYPE_POSSESSIONS slots by default..
// ..due to the possibility of 'hidden' items from client version bias..or, possibly, soul-bound items (WoW?)
if (!removed_list.empty()) {
2013-05-09 10:44:08 -04:00
std::list<uint32>::const_iterator iter = removed_list.begin();
2018-08-13 22:32:36 -04:00
if (iter != removed_list.end()) {
std::stringstream ss("");
ss << "DELETE FROM `inventory` WHERE `charid` = " << client->CharacterID();
ss << " AND `slotid` IN (" << (*iter);
++iter;
2018-08-13 22:32:36 -04:00
while (iter != removed_list.end()) {
ss << ", " << (*iter);
++iter;
}
ss << ")";
database.QueryDatabase(ss.str().c_str());
2013-02-16 16:14:39 -08:00
}
}
2013-05-06 13:07:41 -04:00
auto start = client->GetInv().cursor_cbegin();
auto finish = client->GetInv().cursor_cend();
database.SaveCursor(client->CharacterID(), start, finish);
2013-02-16 16:14:39 -08:00
client->CalcBonuses();
2013-02-16 16:14:39 -08:00
client->Save();
IsRezzed(false);
Save();
database.TransactionCommit();
UpdateEquipmentLight();
UpdateActiveLight();
return;
2013-02-16 16:14:39 -08:00
} //end "not leaving naked corpses"
2013-05-06 13:07:41 -04:00
UpdateEquipmentLight();
UpdateActiveLight();
2018-08-13 22:32:36 -04:00
loot_request_type = LootRequestType::Forbidden;
IsRezzed(false);
2013-02-16 16:14:39 -08:00
Save();
}
void Corpse::MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equipSlot, std::list<uint32> &removedList)
2013-02-16 16:14:39 -08:00
{
AddItem(
inst->GetItem()->ID,
inst->GetCharges(),
equipSlot,
inst->GetAugmentItemID(0),
inst->GetAugmentItemID(1),
inst->GetAugmentItemID(2),
inst->GetAugmentItemID(3),
inst->GetAugmentItemID(4),
inst->GetAugmentItemID(5),
inst->IsAttuned()
);
removedList.push_back(equipSlot);
while (true) {
if (!inst->IsClassBag()) { break; }
if (equipSlot < EQ::invslot::GENERAL_BEGIN || equipSlot > EQ::invslot::slotCursor) { break; }
for (int16 sub_index = EQ::invbag::SLOT_BEGIN; sub_index <= EQ::invbag::SLOT_END; ++sub_index) {
int16 real_bag_slot = EQ::InventoryProfile::CalcSlotId(equipSlot, sub_index);
auto bag_inst = client->GetInv().GetItem(real_bag_slot);
if (bag_inst == nullptr) { continue; }
AddItem(
bag_inst->GetItem()->ID,
bag_inst->GetCharges(),
real_bag_slot,
bag_inst->GetAugmentItemID(0),
bag_inst->GetAugmentItemID(1),
bag_inst->GetAugmentItemID(2),
bag_inst->GetAugmentItemID(3),
bag_inst->GetAugmentItemID(4),
bag_inst->GetAugmentItemID(5),
bag_inst->IsAttuned()
);
removedList.push_back(real_bag_slot);
client->DeleteItemInInventory(real_bag_slot, 0, true, false);
2013-02-16 16:14:39 -08:00
}
break;
2013-02-16 16:14:39 -08:00
}
client->DeleteItemInInventory(equipSlot, 0, true, false);
2013-02-16 16:14:39 -08:00
}
// To be called from LoadFromDBData
Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard)
: Mob("Unnamed_Corpse",
"",
0,
0,
in_gender,
in_race,
in_class,
BT_Humanoid,
in_deity,
in_level,
0,
in_size,
0,
position,
0, // verified for client innate_light value
in_texture,
in_helmtexture,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
EQ::TintProfile(),
0xff,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
false),
2013-02-16 16:14:39 -08:00
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
2013-02-16 16:14:39 -08:00
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)),
loot_cooldown_timer(10)
{
LoadPlayerCorpseDecayTime(in_dbid);
2014-12-05 20:16:13 -08:00
if (!zone->HasGraveyard() || wasAtGraveyard)
2013-02-16 16:14:39 -08:00
corpse_graveyard_timer.Disable();
is_corpse_changed = false;
is_player_corpse = true;
is_locked = false;
being_looted_by = 0xFFFFFFFF;
corpse_db_id = in_dbid;
player_corpse_depop = false;
char_id = in_charid;
2013-02-16 16:14:39 -08:00
itemlist = *in_itemlist;
in_itemlist->clear();
2013-05-06 13:07:41 -04:00
strcpy(corpse_name, in_charname);
2013-02-16 16:14:39 -08:00
strcpy(name, in_charname);
2013-02-16 16:14:39 -08:00
this->copper = in_copper;
this->silver = in_silver;
this->gold = in_gold;
this->platinum = in_plat;
rez_experience = in_rezexp;
for (int i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0;
}
SetPlayerKillItemID(0);
UpdateEquipmentLight();
UpdateActiveLight();
2018-08-13 22:32:36 -04:00
loot_request_type = LootRequestType::Forbidden;
2013-02-16 16:14:39 -08:00
}
Corpse::~Corpse() {
if (is_player_corpse && !(player_corpse_depop && corpse_db_id == 0)) {
Save();
2013-02-16 16:14:39 -08:00
}
ItemList::iterator cur,end;
cur = itemlist.begin();
end = itemlist.end();
for(; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
ServerLootItem_Struct* item = *cur;
safe_delete(item);
}
itemlist.clear();
}
/*
this needs to be called AFTER the entity_id is set
the client does this too, so it's unchangable
*/
void Corpse::CalcCorpseName() {
EntityList::RemoveNumbers(name);
char tmp[64];
if (is_player_corpse){
2013-02-16 16:14:39 -08:00
snprintf(tmp, sizeof(tmp), "'s corpse%d", GetID());
}
else{
2013-02-16 16:14:39 -08:00
snprintf(tmp, sizeof(tmp), "`s_corpse%d", GetID());
}
2013-02-16 16:14:39 -08:00
name[(sizeof(name) - 1) - strlen(tmp)] = 0;
strcat(name, tmp);
}
bool Corpse::Save() {
if (!is_player_corpse)
2013-02-16 16:14:39 -08:00
return true;
if (!is_corpse_changed)
2013-02-16 16:14:39 -08:00
return true;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
uint32 tmp = this->CountItems();
2014-11-22 23:28:20 -06:00
uint32 tmpsize = sizeof(PlayerCorpse_Struct) + (tmp * sizeof(player_lootitem::ServerLootItem_Struct));
PlayerCorpse_Struct* dbpc = (PlayerCorpse_Struct*) new uchar[tmpsize];
2013-02-16 16:14:39 -08:00
memset(dbpc, 0, tmpsize);
dbpc->itemcount = tmp;
dbpc->size = this->size;
dbpc->locked = is_locked;
2013-02-16 16:14:39 -08:00
dbpc->copper = this->copper;
dbpc->silver = this->silver;
dbpc->gold = this->gold;
dbpc->plat = this->platinum;
dbpc->race = this->race;
2013-02-16 16:14:39 -08:00
dbpc->class_ = class_;
dbpc->gender = gender;
dbpc->deity = deity;
dbpc->level = level;
dbpc->texture = this->texture;
dbpc->helmtexture = this->helmtexture;
dbpc->exp = rez_experience;
2013-02-16 16:14:39 -08:00
2016-06-01 04:58:52 -04:00
memcpy(&dbpc->item_tint.Slot, &item_tint.Slot, sizeof(dbpc->item_tint));
2013-02-16 16:14:39 -08:00
dbpc->haircolor = haircolor;
dbpc->beardcolor = beardcolor;
dbpc->eyecolor2 = eyecolor1;
dbpc->hairstyle = hairstyle;
dbpc->face = luclinface;
dbpc->beard = beard;
dbpc->drakkin_heritage = drakkin_heritage;
dbpc->drakkin_tattoo = drakkin_tattoo;
dbpc->drakkin_details = drakkin_details;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
uint32 x = 0;
ItemList::iterator cur, end;
2013-02-16 16:14:39 -08:00
cur = itemlist.begin();
end = itemlist.end();
for (; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
ServerLootItem_Struct* item = *cur;
2014-11-22 23:28:20 -06:00
memcpy((char*)&dbpc->items[x++], (char*)item, sizeof(ServerLootItem_Struct));
2013-02-16 16:14:39 -08:00
}
/* Create New Corpse*/
if (corpse_db_id == 0) {
corpse_db_id = database.SaveCharacterCorpse(char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, consented_guild_id);
2013-02-16 16:14:39 -08:00
}
/* Update Corpse Data */
else{
corpse_db_id = database.UpdateCharacterCorpse(corpse_db_id, char_id, corpse_name, zone->GetZoneID(), zone->GetInstanceID(), dbpc, m_Position, consented_guild_id, IsRezzed());
2013-02-16 16:14:39 -08:00
}
2014-11-22 23:28:20 -06:00
safe_delete_array(dbpc);
2013-02-16 16:14:39 -08:00
return true;
}
void Corpse::Delete() {
if (IsPlayerCorpse() && corpse_db_id != 0)
database.DeleteCharacterCorpse(corpse_db_id);
corpse_db_id = 0;
player_corpse_depop = true;
2013-02-16 16:14:39 -08:00
}
void Corpse::Bury() {
if (IsPlayerCorpse() && corpse_db_id != 0)
database.BuryCharacterCorpse(corpse_db_id);
corpse_db_id = 0;
player_corpse_depop = true;
2013-02-16 16:14:39 -08:00
}
void Corpse::DepopNPCCorpse() {
2013-02-16 16:14:39 -08:00
if (IsNPCCorpse())
player_corpse_depop = true;
2013-02-16 16:14:39 -08:00
}
void Corpse::DepopPlayerCorpse() {
player_corpse_depop = true;
2013-02-16 16:14:39 -08:00
}
void Corpse::AddConsentName(std::string consent_player_name)
{
for (const auto& consented_player_name : consented_player_names) {
if (strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0) {
return;
}
}
consented_player_names.emplace_back(consent_player_name);
}
void Corpse::RemoveConsentName(std::string consent_player_name)
{
consented_player_names.erase(std::remove_if(consented_player_names.begin(), consented_player_names.end(),
[consent_player_name](const std::string& consented_player_name) {
return strcasecmp(consented_player_name.c_str(), consent_player_name.c_str()) == 0;
}
), consented_player_names.end());
}
2013-02-16 16:14:39 -08:00
uint32 Corpse::CountItems() {
return itemlist.size();
}
void Corpse::AddItem(uint32 itemnum, uint16 charges, int16 slot, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6, uint8 attuned) {
2013-02-16 16:14:39 -08:00
if (!database.GetItem(itemnum))
return;
is_corpse_changed = true;
2016-05-25 16:10:28 -04:00
auto item = new ServerLootItem_Struct;
2014-12-05 20:16:13 -08:00
2013-02-16 16:14:39 -08:00
memset(item, 0, sizeof(ServerLootItem_Struct));
item->item_id = itemnum;
item->charges = charges;
2014-11-22 23:28:20 -06:00
item->equip_slot = slot;
item->aug_1=aug1;
item->aug_2=aug2;
item->aug_3=aug3;
item->aug_4=aug4;
item->aug_5=aug5;
item->aug_6=aug6;
item->attuned=attuned;
2013-02-16 16:14:39 -08:00
itemlist.push_back(item);
UpdateEquipmentLight();
2013-02-16 16:14:39 -08:00
}
ServerLootItem_Struct* Corpse::GetItem(uint16 lootslot, ServerLootItem_Struct** bag_item_data) {
ServerLootItem_Struct *sitem = nullptr, *sitem2 = nullptr;
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
ItemList::iterator cur,end;
cur = itemlist.begin();
end = itemlist.end();
for(; cur != end; ++cur) {
if((*cur)->lootslot == lootslot) {
2013-02-16 16:14:39 -08:00
sitem = *cur;
break;
}
}
if (sitem && bag_item_data && EQ::InventoryProfile::SupportsContainers(sitem->equip_slot)) {
int16 bagstart = EQ::InventoryProfile::CalcSlotId(sitem->equip_slot, EQ::invbag::SLOT_BEGIN);
2013-02-16 16:14:39 -08:00
cur = itemlist.begin();
end = itemlist.end();
for (; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
sitem2 = *cur;
if (sitem2->equip_slot >= bagstart && sitem2->equip_slot < bagstart + 10) {
2014-11-22 23:28:20 -06:00
bag_item_data[sitem2->equip_slot - bagstart] = sitem2;
2013-02-16 16:14:39 -08:00
}
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return sitem;
}
uint32 Corpse::GetWornItem(int16 equipSlot) const {
ItemList::const_iterator cur,end;
cur = itemlist.begin();
end = itemlist.end();
for(; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
ServerLootItem_Struct* item = *cur;
if (item->equip_slot == equipSlot) {
2013-02-16 16:14:39 -08:00
return item->item_id;
}
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return 0;
}
void Corpse::RemoveItem(uint16 lootslot) {
2013-02-16 16:14:39 -08:00
if (lootslot == 0xFFFF)
return;
ItemList::iterator cur,end;
cur = itemlist.begin();
end = itemlist.end();
for (; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
ServerLootItem_Struct* sitem = *cur;
if (sitem->lootslot == lootslot) {
2013-02-16 16:14:39 -08:00
RemoveItem(sitem);
return;
}
}
}
void Corpse::RemoveItem(ServerLootItem_Struct* item_data)
{
for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) {
auto sitem = *iter;
if (sitem != item_data) { continue; }
2013-02-16 16:14:39 -08:00
is_corpse_changed = true;
itemlist.erase(iter);
2013-05-06 13:07:41 -04:00
uint8 material = EQ::InventoryProfile::CalcMaterialFromSlot(sitem->equip_slot); // autos to unsigned char
if (material != EQ::textures::materialInvalid)
SendWearChange(material);
2013-05-06 13:07:41 -04:00
UpdateEquipmentLight();
if (UpdateActiveLight())
SendAppearancePacket(AT_Light, GetActiveLightType());
safe_delete(sitem);
return;
2013-02-16 16:14:39 -08:00
}
}
void Corpse::SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum) {
this->copper = in_copper;
this->silver = in_silver;
this->gold = in_gold;
this->platinum = in_platinum;
is_corpse_changed = true;
2013-02-16 16:14:39 -08:00
}
void Corpse::RemoveCash() {
this->copper = 0;
this->silver = 0;
this->gold = 0;
this->platinum = 0;
is_corpse_changed = true;
2013-02-16 16:14:39 -08:00
}
bool Corpse::IsEmpty() const {
if (copper != 0 || silver != 0 || gold != 0 || platinum != 0)
return false;
return itemlist.empty();
2013-02-16 16:14:39 -08:00
}
bool Corpse::Process() {
if (player_corpse_depop)
2013-02-16 16:14:39 -08:00
return false;
2013-05-06 13:07:41 -04:00
if (corpse_delay_timer.Check()) {
2014-12-05 20:16:13 -08:00
for (int i = 0; i < MAX_LOOTERS; i++)
allowed_looters[i] = 0;
2013-02-16 16:14:39 -08:00
corpse_delay_timer.Disable();
return true;
}
if (corpse_graveyard_timer.Check()) {
if (zone->HasGraveyard()) {
2013-02-16 16:14:39 -08:00
Save();
player_corpse_depop = true;
database.SendCharacterCorpseToGraveyard(corpse_db_id, zone->graveyard_zoneid(),
(zone->GetZoneID() == zone->graveyard_zoneid()) ? zone->GetInstanceID() : 0, zone->GetGraveyardPoint());
2013-02-16 16:14:39 -08:00
corpse_graveyard_timer.Disable();
2016-05-25 16:10:28 -04:00
auto pack = new ServerPacket(ServerOP_SpawnPlayerCorpse, sizeof(SpawnPlayerCorpse_Struct));
2013-02-16 16:14:39 -08:00
SpawnPlayerCorpse_Struct* spc = (SpawnPlayerCorpse_Struct*)pack->pBuffer;
spc->player_corpse_id = corpse_db_id;
2013-02-16 16:14:39 -08:00
spc->zone_id = zone->graveyard_zoneid();
worldserver.SendPacket(pack);
safe_delete(pack);
2019-09-01 23:54:24 -05:00
LogDebug("Moved [{}] player corpse to the designated graveyard in zone [{}]", this->GetName(), database.GetZoneName(zone->graveyard_zoneid()));
corpse_db_id = 0;
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
corpse_graveyard_timer.Disable();
return false;
}
/*
if(corpse_res_timer.Check()) {
can_rez = false;
corpse_res_timer.Disable();
}
*/
/* This is when a corpse hits decay timer and does checks*/
if (corpse_decay_timer.Check()) {
/* NPC */
if (IsNPCCorpse()){
corpse_decay_timer.Disable();
return false;
}
/* Client */
if (!RuleB(Zone, EnableShadowrest)){
2013-02-16 16:14:39 -08:00
Delete();
}
2013-02-16 16:14:39 -08:00
else {
if (database.BuryCharacterCorpse(corpse_db_id)) {
2013-02-16 16:14:39 -08:00
Save();
player_corpse_depop = true;
corpse_db_id = 0;
2019-09-01 23:54:24 -05:00
LogDebug("Tagged [{}] player corpse has buried", this->GetName());
2013-02-16 16:14:39 -08:00
}
else {
2019-09-01 22:05:44 -05:00
LogError("Unable to bury [{}] player corpse", this->GetName());
2013-02-16 16:14:39 -08:00
return true;
}
}
corpse_decay_timer.Disable();
return false;
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
return true;
}
void Corpse::SetDecayTimer(uint32 decaytime) {
if (decaytime == 0)
corpse_decay_timer.Trigger();
else
corpse_decay_timer.Start(decaytime);
}
bool Corpse::CanPlayerLoot(int charid) {
uint8 looters = 0;
for (int i = 0; i < MAX_LOOTERS; i++) {
if (allowed_looters[i] != 0){
looters++;
}
2013-02-16 16:14:39 -08:00
if (allowed_looters[i] == charid)
2013-02-16 16:14:39 -08:00
return true;
}
/* If we have no looters, obviously client can loot */
2014-12-05 20:16:13 -08:00
return looters == 0;
2013-02-16 16:14:39 -08:00
}
void Corpse::AllowPlayerLoot(Mob *them, uint8 slot) {
2013-02-16 16:14:39 -08:00
if(slot >= MAX_LOOTERS)
return;
2013-05-04 18:06:58 -07:00
if(them == nullptr || !them->IsClient())
2013-02-16 16:14:39 -08:00
return;
allowed_looters[slot] = them->CastToClient()->CharacterID();
2013-02-16 16:14:39 -08:00
}
void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* app) {
2018-08-13 22:32:36 -04:00
if (!client)
return;
2013-05-09 10:44:08 -04:00
// Added 12/08. Started compressing loot struct on live.
if(player_corpse_depop) {
SendLootReqErrorPacket(client, LootResponse::SomeoneElse);
2013-02-16 16:14:39 -08:00
return;
}
2018-08-13 22:32:36 -04:00
if(IsPlayerCorpse() && !corpse_db_id) { // really should try to resave in this case
2013-02-16 16:14:39 -08:00
// SendLootReqErrorPacket(client, 0);
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "Warning: Corpse's dbid = 0! Corpse will not survive zone shutdown!");
std::cout << "Error: PlayerCorpse::MakeLootRequestPackets: dbid = 0!" << std::endl;
2013-02-16 16:14:39 -08:00
// return;
}
if(is_locked && client->Admin() < 100) {
SendLootReqErrorPacket(client, LootResponse::SomeoneElse);
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "Error: Corpse locked by GM.");
2013-02-16 16:14:39 -08:00
return;
}
2018-08-13 22:32:36 -04:00
if(!being_looted_by || (being_looted_by != 0xFFFFFFFF && !entity_list.GetID(being_looted_by)))
2014-12-05 20:16:13 -08:00
being_looted_by = 0xFFFFFFFF;
2013-02-16 16:14:39 -08:00
if (DistanceSquaredNoZ(client->GetPosition(), m_Position) > 625) {
SendLootReqErrorPacket(client, LootResponse::TooFar);
return;
}
2018-08-13 22:32:36 -04:00
if (being_looted_by != 0xFFFFFFFF && being_looted_by != client->GetID()) {
SendLootReqErrorPacket(client, LootResponse::SomeoneElse);
2018-08-13 22:32:36 -04:00
return;
}
2018-08-13 22:32:36 -04:00
// all loot session disqualifiers should occur before this point as not to interfere with any current looter
loot_request_type = LootRequestType::Forbidden;
// loot_request_type is scoped to class Corpse and reset on a per-loot session basis
if (client->GetGM()) {
if (client->Admin() >= 100)
loot_request_type = LootRequestType::GMAllowed;
else
loot_request_type = LootRequestType::GMPeek;
}
2018-08-13 22:32:36 -04:00
else {
if (IsPlayerCorpse()) {
if (char_id == client->CharacterID()) {
loot_request_type = LootRequestType::Self;
}
else if (CanPlayerLoot(client->CharacterID())) {
if (GetPlayerKillItem() == -1)
loot_request_type = LootRequestType::AllowedPVPAll;
else if (GetPlayerKillItem() == 1)
loot_request_type = LootRequestType::AllowedPVPSingle;
else if (GetPlayerKillItem() > 1)
loot_request_type = LootRequestType::AllowedPVPDefined;
}
}
else if ((IsNPCCorpse() || become_npc) && CanPlayerLoot(client->CharacterID())) {
loot_request_type = LootRequestType::AllowedPVE;
}
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
LogInventory("MakeLootRequestPackets() LootRequestType [{}] for [{}]", (int) loot_request_type, client->GetName());
2018-08-13 22:32:36 -04:00
if (loot_request_type == LootRequestType::Forbidden) {
SendLootReqErrorPacket(client, LootResponse::NotAtThisTime);
return;
}
2013-02-16 16:14:39 -08:00
2018-08-13 22:32:36 -04:00
being_looted_by = client->GetID();
client->CommonBreakInvisible(); // we should be "all good" so lets break invis now instead of earlier before all error checking is done
// process coin
bool loot_coin = false;
std::string tmp;
if (database.GetVariable("LootCoin", tmp))
loot_coin = (tmp[0] == 1 && tmp[1] == '\0');
if (loot_request_type == LootRequestType::GMPeek || loot_request_type == LootRequestType::GMAllowed) {
2019-08-11 00:00:55 -05:00
client->Message(Chat::Yellow, "This corpse contains %u platinum, %u gold, %u silver and %u copper.",
2018-08-13 22:32:36 -04:00
GetPlatinum(), GetGold(), GetSilver(), GetCopper());
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct));
2018-08-13 22:32:36 -04:00
moneyOnCorpseStruct* d = (moneyOnCorpseStruct*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
2018-08-13 22:32:36 -04:00
d->response = static_cast<uint8>(LootResponse::Normal);
d->unknown1 = 0x42;
d->unknown2 = 0xef;
d->copper = 0;
d->silver = 0;
d->gold = 0;
d->platinum = 0;
2013-02-16 16:14:39 -08:00
outapp->priority = 6;
2013-05-06 13:07:41 -04:00
client->QueuePacket(outapp);
2018-08-13 22:32:36 -04:00
2013-02-16 16:14:39 -08:00
safe_delete(outapp);
2018-08-13 22:32:36 -04:00
}
else {
auto outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct));
moneyOnCorpseStruct* d = (moneyOnCorpseStruct*)outapp->pBuffer;
2013-02-16 16:14:39 -08:00
2018-08-13 22:32:36 -04:00
d->response = static_cast<uint8>(LootResponse::Normal);
d->unknown1 = 0x42;
d->unknown2 = 0xef;
Group* cgroup = client->GetGroup();
// this can be reworked into a switch and/or massaged to include specialized pve loot rules based on 'LootRequestType'
if (!IsPlayerCorpse() && client->IsGrouped() && client->AutoSplitEnabled() && cgroup) {
d->copper = 0;
d->silver = 0;
d->gold = 0;
d->platinum = 0;
cgroup->SplitMoney(GetCopper(), GetSilver(), GetGold(), GetPlatinum(), client);
}
else {
d->copper = GetCopper();
d->silver = GetSilver();
d->gold = GetGold();
d->platinum = GetPlatinum();
client->AddMoneyToPP(GetCopper(), GetSilver(), GetGold(), GetPlatinum(), false);
2013-02-16 16:14:39 -08:00
}
2018-08-13 22:32:36 -04:00
RemoveCash();
Save();
2013-05-06 13:07:41 -04:00
2018-08-13 22:32:36 -04:00
outapp->priority = 6;
client->QueuePacket(outapp);
2013-02-16 16:14:39 -08:00
2018-08-13 22:32:36 -04:00
safe_delete(outapp);
}
// process items
auto timestamps = database.GetItemRecastTimestamps(client->CharacterID());
if (loot_request_type == LootRequestType::AllowedPVPDefined) {
auto pkitemid = GetPlayerKillItem();
auto pkitem = database.GetItem(pkitemid);
auto pkinst = database.CreateItem(pkitem, pkitem->MaxCharges);
if (pkinst) {
if (pkitem->RecastDelay)
pkinst->SetRecastTimestamp(timestamps.count(pkitem->RecastType) ? timestamps.at(pkitem->RecastType) : 0);
LogInventory("MakeLootRequestPackets() Slot [{}], Item [{}]", EQ::invslot::CORPSE_BEGIN, pkitem->Name);
2018-08-13 22:32:36 -04:00
client->SendItemPacket(EQ::invslot::CORPSE_BEGIN, pkinst, ItemPacketLoot);
2018-08-13 22:32:36 -04:00
safe_delete(pkinst);
2013-02-16 16:14:39 -08:00
}
2018-08-13 22:32:36 -04:00
else {
LogInventory("MakeLootRequestPackets() PlayerKillItem [{}] not found", pkitemid);
2013-02-16 16:14:39 -08:00
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "PlayerKillItem (id: %i) could not be found!", pkitemid);
2018-08-13 22:32:36 -04:00
}
2013-05-06 13:07:41 -04:00
2018-08-13 22:32:36 -04:00
client->QueuePacket(app);
return;
}
auto loot_slot = EQ::invslot::CORPSE_BEGIN;
2018-08-13 22:32:36 -04:00
auto corpse_mask = client->GetInv().GetLookup()->CorpseBitmask;
for (auto item_data : itemlist) {
// every loot session must either set all items' lootslots to 'invslot::SLOT_INVALID'
// or to a valid enumerated client-versioned corpse slot (lootslot is not equip_slot)
item_data->lootslot = 0xFFFF;
// align server and client corpse slot mappings so translators can function properly
while (loot_slot <= EQ::invslot::CORPSE_END && (((uint64)1 << loot_slot) & corpse_mask) == 0)
2018-08-13 22:32:36 -04:00
++loot_slot;
if (loot_slot > EQ::invslot::CORPSE_END)
2018-08-13 22:32:36 -04:00
continue;
if (IsPlayerCorpse()) {
if (loot_request_type == LootRequestType::AllowedPVPSingle && loot_slot != EQ::invslot::CORPSE_BEGIN)
2018-08-13 22:32:36 -04:00
continue;
if (item_data->equip_slot < EQ::invslot::POSSESSIONS_BEGIN || item_data->equip_slot > EQ::invslot::POSSESSIONS_END)
2018-08-13 22:32:36 -04:00
continue;
2013-02-16 16:14:39 -08:00
}
2018-08-13 22:32:36 -04:00
const auto *item = database.GetItem(item_data->item_id);
auto inst = database.CreateItem(
item,
item_data->charges,
item_data->aug_1,
item_data->aug_2,
item_data->aug_3,
item_data->aug_4,
item_data->aug_5,
item_data->aug_6,
item_data->attuned
);
if (!inst)
continue;
if (item->RecastDelay)
inst->SetRecastTimestamp(timestamps.count(item->RecastType) ? timestamps.at(item->RecastType) : 0);
LogInventory("MakeLootRequestPackets() Slot [{}], Item [{}]", loot_slot, item->Name);
2018-08-13 22:32:36 -04:00
client->SendItemPacket(loot_slot, inst, ItemPacketLoot);
safe_delete(inst);
item_data->lootslot = loot_slot++;
2013-02-16 16:14:39 -08:00
}
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
// Disgrace: Client seems to require that we send the packet back...
client->QueuePacket(app);
// This is required for the 'Loot All' feature to work for SoD clients. I expect it is to tell the client that the
// server has now sent all the items on the corpse.
if (client->ClientVersion() >= EQ::versions::ClientVersion::SoD)
SendLootReqErrorPacket(client, LootResponse::LootAll);
2013-02-16 16:14:39 -08:00
}
2016-12-16 16:03:44 -05:00
void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
{
2018-08-13 22:32:36 -04:00
if (!client)
return;
auto lootitem = (LootingItem_Struct *)app->pBuffer;
2013-02-16 16:14:39 -08:00
LogInventory("LootItem() LootRequestType [{}], Slot [{}] for [{}]", (int) loot_request_type, lootitem->slot_id, client->GetName());
2018-08-13 22:32:36 -04:00
if (loot_request_type < LootRequestType::GMAllowed) { // LootRequestType::Forbidden and LootRequestType::GMPeek
2018-08-13 22:32:36 -04:00
client->QueuePacket(app);
SendEndLootErrorPacket(client);
// unlock corpse for others
if (IsBeingLootedBy(client))
ResetLooter();
return;
}
if (!loot_cooldown_timer.Check()) {
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
2016-12-16 16:03:44 -05:00
// unlock corpse for others
2016-12-16 16:02:42 -05:00
if (IsBeingLootedBy(client))
ResetLooter();
2013-02-16 16:14:39 -08:00
return;
}
/* To prevent item loss for a player using 'Loot All' who doesn't have inventory space for all their items. */
if (RuleB(Character, CheckCursorEmptyWhenLooting) && !client->GetInv().CursorEmpty()) {
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "You may not loot an item while you have an item on your cursor.");
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
/* Unlock corpse for others */
2016-12-16 16:02:42 -05:00
if (IsBeingLootedBy(client))
ResetLooter();
2013-02-16 16:14:39 -08:00
return;
}
2016-12-16 16:02:42 -05:00
if (!IsBeingLootedBy(client)) {
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
return;
}
2016-12-16 16:03:44 -05:00
if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc &&
2018-08-13 22:32:36 -04:00
(char_id != client->CharacterID() && client->Admin() < 150)) {
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "Error: This is a player corpse and you dont own it.");
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
return;
}
if (is_locked && client->Admin() < 100) {
client->QueuePacket(app);
SendLootReqErrorPacket(client, LootResponse::SomeoneElse);
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "Error: Corpse locked by GM.");
2013-02-16 16:14:39 -08:00
return;
}
2016-12-16 16:03:44 -05:00
if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) &&
2018-08-13 22:32:36 -04:00
GetPlayerKillItem() == 0) {
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "Error: You cannot loot any more items from this corpse.");
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
2016-12-16 16:02:42 -05:00
ResetLooter();
2013-02-16 16:14:39 -08:00
return;
}
2016-12-16 16:02:42 -05:00
const EQ::ItemData *item = nullptr;
EQ::ItemInstance *inst = nullptr;
ServerLootItem_Struct *item_data = nullptr, *bag_item_data[10] = {};
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
memset(bag_item_data, 0, sizeof(bag_item_data));
2016-12-16 16:03:44 -05:00
if (GetPlayerKillItem() > 1) {
item = database.GetItem(GetPlayerKillItem());
2018-08-13 22:32:36 -04:00
}
else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1) {
2016-12-16 16:03:44 -05:00
item_data =
2018-08-13 22:32:36 -04:00
GetItem(lootitem->slot_id); // dont allow them to loot entire bags of items as pvp reward
}
else {
item_data = GetItem(lootitem->slot_id, bag_item_data);
}
2013-02-16 16:14:39 -08:00
2016-12-16 16:03:44 -05:00
if (GetPlayerKillItem() <= 1 && item_data != 0) {
2013-02-16 16:14:39 -08:00
item = database.GetItem(item_data->item_id);
}
2013-05-06 13:07:41 -04:00
if (item != 0) {
2016-12-16 16:03:44 -05:00
if (item_data) {
inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1,
2018-08-13 22:32:36 -04:00
item_data->aug_2, item_data->aug_3, item_data->aug_4,
item_data->aug_5, item_data->aug_6, item_data->attuned);
}
else {
2013-02-16 16:14:39 -08:00
inst = database.CreateItem(item);
}
2013-02-16 16:14:39 -08:00
}
if (client && inst) {
if (client->CheckLoreConflict(item)) {
2019-08-11 00:14:02 -05:00
client->MessageString(Chat::White, LOOT_LORE_ERROR);
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
2016-12-16 16:02:42 -05:00
ResetLooter();
2013-02-16 16:14:39 -08:00
delete inst;
return;
}
if (inst->IsAugmented()) {
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
EQ::ItemInstance *itm = inst->GetAugment(i);
if (itm) {
if (client->CheckLoreConflict(itm->GetItem())) {
2019-08-11 00:14:02 -05:00
client->MessageString(Chat::White, LOOT_LORE_ERROR);
client->QueuePacket(app);
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
2016-12-16 16:02:42 -05:00
ResetLooter();
2013-02-16 16:14:39 -08:00
delete inst;
return;
}
}
}
}
char buf[88];
2016-08-05 22:14:20 -04:00
char q_corpse_name[64];
strcpy(q_corpse_name, corpse_name);
2016-12-16 16:03:44 -05:00
snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(),
2018-08-13 22:32:36 -04:00
EntityList::RemoveNumbers(q_corpse_name));
2013-02-16 16:14:39 -08:00
buf[87] = '\0';
std::vector<EQ::Any> args;
args.push_back(inst);
args.push_back(this);
if (parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args) != 0) {
lootitem->auto_loot = -1;
2019-08-11 00:14:02 -05:00
client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name);
client->QueuePacket(app);
delete inst;
return;
}
2018-08-13 22:32:36 -04:00
// do we want this to have a fail option too?
parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0);
2013-02-16 16:14:39 -08:00
// safe to ACK now
client->QueuePacket(app);
if (!IsPlayerCorpse() && RuleB(Character, EnableDiscoveredItems)) {
if (client && !client->GetGM() && !client->IsDiscovered(inst->GetItem()->ID))
client->DiscoverItem(inst->GetItem()->ID);
2013-02-16 16:14:39 -08:00
}
if (zone->adv_data) {
2016-12-16 16:03:44 -05:00
ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct *)zone->adv_data;
if (ad->type == Adventure_Collect && !IsPlayerCorpse()) {
if (ad->data_id == inst->GetItem()->ID) {
2013-02-16 16:14:39 -08:00
zone->DoAdventureCountIncrease();
}
}
}
/* First add it to the looter - this will do the bag contents too */
if (lootitem->auto_loot > 0) {
if (!client->AutoPutLootInInventory(*inst, true, true, bag_item_data))
client->PutLootInInventory(EQ::invslot::slotCursor, *inst, bag_item_data);
2018-08-13 22:32:36 -04:00
}
else {
client->PutLootInInventory(EQ::invslot::slotCursor, *inst, bag_item_data);
2013-02-16 16:14:39 -08:00
}
/* Update any tasks that have an activity to loot this item */
if (RuleB(TaskSystem, EnableTaskSystem))
2013-02-16 16:14:39 -08:00
client->UpdateTasksForItem(ActivityLoot, item->ID);
/* Remove it from Corpse */
2016-12-16 16:03:44 -05:00
if (item_data) {
/* Delete needs to be before RemoveItem because its deletes the pointer for
2018-08-13 22:32:36 -04:00
* item_data/bag_item_data */
2016-12-16 16:03:44 -05:00
database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot,
2018-08-13 22:32:36 -04:00
item_data->item_id);
/* Delete Item Instance */
RemoveItem(item_data->lootslot);
}
/* Remove Bag Contents */
if (item->IsClassBag() && (GetPlayerKillItem() != -1 || GetPlayerKillItem() != 1)) {
for (int i = EQ::invbag::SLOT_BEGIN; i <= EQ::invbag::SLOT_END; i++) {
if (bag_item_data[i]) {
2016-12-16 16:03:44 -05:00
/* Delete needs to be before RemoveItem because its deletes the pointer for
2018-08-13 22:32:36 -04:00
* item_data/bag_item_data */
2016-12-16 16:03:44 -05:00
database.DeleteItemOffCharacterCorpse(this->corpse_db_id,
2018-08-13 22:32:36 -04:00
bag_item_data[i]->equip_slot,
bag_item_data[i]->item_id);
/* Delete Item Instance */
RemoveItem(bag_item_data[i]);
2013-02-16 16:14:39 -08:00
}
}
}
2013-05-06 13:07:41 -04:00
2014-12-28 12:49:14 -05:00
if (GetPlayerKillItem() != -1) {
SetPlayerKillItemID(0);
}
2013-05-06 13:07:41 -04:00
2016-12-16 16:03:44 -05:00
/* Send message with item link to groups and such */
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemInst);
2016-12-16 16:03:44 -05:00
linker.SetItemInst(inst);
2014-12-28 12:49:14 -05:00
2018-02-24 09:08:11 -05:00
linker.GenerateLink();
2014-12-28 12:49:14 -05:00
2019-08-11 00:14:02 -05:00
client->MessageString(Chat::Loot, LOOTED_MESSAGE, linker.Link().c_str());
2014-12-28 12:49:14 -05:00
2016-12-16 16:03:44 -05:00
if (!IsPlayerCorpse()) {
2013-02-16 16:14:39 -08:00
Group *g = client->GetGroup();
2016-12-16 16:03:44 -05:00
if (g != nullptr) {
2019-08-11 00:14:02 -05:00
g->GroupMessageString(client, Chat::Loot, OTHER_LOOTED_MESSAGE,
2018-08-13 22:32:36 -04:00
client->GetName(), linker.Link().c_str());
}
else {
2013-02-16 16:14:39 -08:00
Raid *r = client->GetRaid();
2016-12-16 16:03:44 -05:00
if (r != nullptr) {
2019-08-11 00:14:02 -05:00
r->RaidMessageString(client, Chat::Loot, OTHER_LOOTED_MESSAGE,
2018-08-13 22:32:36 -04:00
client->GetName(), linker.Link().c_str());
2013-02-16 16:14:39 -08:00
}
}
}
2018-08-13 22:32:36 -04:00
}
else {
2013-02-16 16:14:39 -08:00
SendEndLootErrorPacket(client);
safe_delete(inst);
return;
}
2016-12-16 16:03:44 -05:00
if (IsPlayerCorpse()) {
2013-02-16 16:14:39 -08:00
client->SendItemLink(inst);
2018-08-13 22:32:36 -04:00
}
else {
2013-02-16 16:14:39 -08:00
client->SendItemLink(inst, true);
}
2013-02-16 16:14:39 -08:00
safe_delete(inst);
}
void Corpse::EndLoot(Client* client, const EQApplicationPacket* app) {
2016-05-25 16:10:28 -04:00
auto outapp = new EQApplicationPacket;
2013-02-16 16:14:39 -08:00
outapp->SetOpcode(OP_LootComplete);
outapp->size = 0;
client->QueuePacket(outapp);
safe_delete(outapp);
2013-05-06 13:07:41 -04:00
this->being_looted_by = 0xFFFFFFFF;
2013-02-16 16:14:39 -08:00
if (this->IsEmpty())
Delete();
else
Save();
}
void Corpse::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) {
2013-02-16 16:14:39 -08:00
Mob::FillSpawnStruct(ns, ForWho);
2013-05-06 13:07:41 -04:00
2013-02-16 16:14:39 -08:00
ns->spawn.max_hp = 120;
ns->spawn.NPC = 2;
UpdateActiveLight();
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
2013-02-16 16:14:39 -08:00
}
void Corpse::QueryLoot(Client* to) {
2013-02-16 16:14:39 -08:00
int x = 0, y = 0; // x = visible items, y = total items
to->Message(Chat::White, "Coin: %ip, %ig, %is, %ic", platinum, gold, silver, copper);
2013-02-16 16:14:39 -08:00
ItemList::iterator cur,end;
cur = itemlist.begin();
end = itemlist.end();
int corpselootlimit = to->GetInv().GetLookup()->InventoryTypeSize.Corpse;
2013-02-16 16:14:39 -08:00
for(; cur != end; ++cur) {
2013-02-16 16:14:39 -08:00
ServerLootItem_Struct* sitem = *cur;
2013-05-09 10:44:08 -04:00
if (IsPlayerCorpse()) {
if (sitem->equip_slot >= EQ::invbag::GENERAL_BAGS_BEGIN && sitem->equip_slot <= EQ::invbag::CURSOR_BAG_END)
2013-05-09 10:44:08 -04:00
sitem->lootslot = 0xFFFF;
else
x < corpselootlimit ? sitem->lootslot = x : sitem->lootslot = 0xFFFF;
2013-05-06 13:07:41 -04:00
const EQ::ItemData* item = database.GetItem(sitem->item_id);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
if (item)
2014-11-22 23:28:20 -06:00
to->Message((sitem->lootslot == 0xFFFF), "LootSlot: %i (EquipSlot: %i) Item: %s (%d), Count: %i", static_cast<int16>(sitem->lootslot), sitem->equip_slot, item->Name, item->ID, sitem->charges);
2013-05-09 10:44:08 -04:00
else
to->Message((sitem->lootslot == 0xFFFF), "Error: 0x%04x", sitem->item_id);
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
if (sitem->lootslot != 0xFFFF)
x++;
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
y++;
}
else {
sitem->lootslot=y;
const EQ::ItemData* item = database.GetItem(sitem->item_id);
2013-05-06 13:07:41 -04:00
2013-05-09 10:44:08 -04:00
if (item)
to->Message(Chat::White, "LootSlot: %i Item: %s (%d), Count: %i", sitem->lootslot, item->Name, item->ID, sitem->charges);
2013-05-09 10:44:08 -04:00
else
to->Message(Chat::White, "Error: 0x%04x", sitem->item_id);
2013-02-16 16:14:39 -08:00
2013-05-09 10:44:08 -04:00
y++;
}
2013-02-16 16:14:39 -08:00
}
2013-05-09 10:44:08 -04:00
if (IsPlayerCorpse()) {
to->Message(Chat::White, "%i visible %s (%i total) on %s (DBID: %i).", x, x==1?"item":"items", y, this->GetName(), this->GetCorpseDBID());
2013-05-09 10:44:08 -04:00
}
else {
to->Message(Chat::White, "%i %s on %s.", y, y==1?"item":"items", this->GetName());
2013-05-09 10:44:08 -04:00
}
2013-02-16 16:14:39 -08:00
}
bool Corpse::Summon(Client* client, bool spell, bool CheckDistance) {
2013-02-16 16:14:39 -08:00
uint32 dist2 = 10000; // pow(100, 2);
if (!spell) {
if (this->GetCharID() == client->CharacterID()) {
if (IsLocked() && client->Admin() < 100) {
2019-08-11 00:00:55 -05:00
client->Message(Chat::Red, "That corpse is locked by a GM.");
2013-02-16 16:14:39 -08:00
return false;
}
if (!CheckDistance || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) {
2013-02-16 16:14:39 -08:00
GMMove(client->GetX(), client->GetY(), client->GetZ());
is_corpse_changed = true;
2013-02-16 16:14:39 -08:00
}
else {
client->MessageString(Chat::Red, CORPSE_TOO_FAR);
2013-02-16 16:14:39 -08:00
return false;
}
}
2013-05-06 13:07:41 -04:00
else
2013-02-16 16:14:39 -08:00
{
bool consented = false;
for (const auto& consented_player_name : consented_player_names) {
if (strcasecmp(client->GetName(), consented_player_name.c_str()) == 0) {
consented = true;
break;
}
}
if (!consented && consented_guild_id && consented_guild_id != GUILD_NONE) {
if (client->GuildID() == consented_guild_id) {
2020-01-29 18:34:33 -05:00
consented = true;
}
}
if (!consented && consented_group_id) {
2020-01-29 18:34:33 -05:00
Group* grp = client->GetGroup();
if (grp && grp->GetID() == consented_group_id) {
2020-01-29 18:34:33 -05:00
consented = true;
}
}
if (!consented && consented_raid_id) {
2020-01-29 18:34:33 -05:00
Raid* raid = client->GetRaid();
if (raid && raid->GetID() == consented_raid_id) {
2013-02-16 16:14:39 -08:00
consented = true;
}
}
if (consented) {
if (!CheckDistance || (DistanceSquaredNoZ(m_Position, client->GetPosition()) <= dist2)) {
GMMove(client->GetX(), client->GetY(), client->GetZ());
is_corpse_changed = true;
}
else {
client->MessageString(Chat::Red, CORPSE_TOO_FAR);
return false;
}
}
else {
client->MessageString(Chat::Red, CONSENT_DENIED);
2013-02-16 16:14:39 -08:00
return false;
}
}
}
else {
GMMove(client->GetX(), client->GetY(), client->GetZ());
is_corpse_changed = true;
2013-02-16 16:14:39 -08:00
}
Save();
return true;
}
void Corpse::CompleteResurrection(){
rez_experience = 0;
is_corpse_changed = true;
2013-02-16 16:14:39 -08:00
this->Save();
}
void Corpse::Spawn() {
2016-05-25 16:10:28 -04:00
auto app = new EQApplicationPacket;
2013-02-16 16:14:39 -08:00
this->CreateSpawnPacket(app, this);
entity_list.QueueClients(this, app);
safe_delete(app);
}
2018-12-31 03:18:59 -06:00
uint32 Corpse::GetEquippedItemFromTextureSlot(uint8 material_slot) const {
int16 invslot;
2013-05-06 13:07:41 -04:00
if (material_slot > EQ::textures::LastTexture) {
2016-10-16 21:36:39 -04:00
return 0;
2013-02-16 16:14:39 -08:00
}
invslot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
2016-10-16 21:36:39 -04:00
if(invslot == INVALID_INDEX) // GetWornItem() should be returning a 0 for any invalid index...
return 0;
2013-02-16 16:14:39 -08:00
return GetWornItem(invslot);
}
uint32 Corpse::GetEquipmentColor(uint8 material_slot) const {
const EQ::ItemData *item = nullptr;
2013-02-16 16:14:39 -08:00
if (material_slot > EQ::textures::LastTexture) {
2013-02-16 16:14:39 -08:00
return 0;
}
2018-12-31 03:18:59 -06:00
item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
2016-10-16 21:36:39 -04:00
if(item != 0) {
2016-06-01 04:58:52 -04:00
return (item_tint.Slot[material_slot].UseTint ? item_tint.Slot[material_slot].Color : item->Color);
2013-02-16 16:14:39 -08:00
}
return 0;
}
void Corpse::UpdateEquipmentLight()
{
m_Light.Type[EQ::lightsource::LightEquipment] = 0;
m_Light.Level[EQ::lightsource::LightEquipment] = 0;
for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) {
if ((*iter)->equip_slot < EQ::invslot::EQUIPMENT_BEGIN || (*iter)->equip_slot > EQ::invslot::EQUIPMENT_END) { continue; }
if ((*iter)->equip_slot == EQ::invslot::slotAmmo) { continue; }
auto item = database.GetItem((*iter)->item_id);
if (item == nullptr) { continue; }
if (EQ::lightsource::IsLevelGreater(item->Light, m_Light.Type[EQ::lightsource::LightEquipment]))
m_Light.Type[EQ::lightsource::LightEquipment] = item->Light;
}
uint8 general_light_type = 0;
for (auto iter = itemlist.begin(); iter != itemlist.end(); ++iter) {
if ((*iter)->equip_slot < EQ::invslot::GENERAL_BEGIN || (*iter)->equip_slot > EQ::invslot::GENERAL_END) { continue; }
auto item = database.GetItem((*iter)->item_id);
if (item == nullptr) { continue; }
if (!item->IsClassCommon()) { continue; }
2015-02-27 19:28:28 -05:00
if (item->Light < 9 || item->Light > 13) { continue; }
if (EQ::lightsource::TypeToLevel(item->Light))
general_light_type = item->Light;
}
if (EQ::lightsource::IsLevelGreater(general_light_type, m_Light.Type[EQ::lightsource::LightEquipment]))
m_Light.Type[EQ::lightsource::LightEquipment] = general_light_type;
m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]);
}
void Corpse::AddLooter(Mob* who) {
for (int i = 0; i < MAX_LOOTERS; i++) {
if (allowed_looters[i] == 0) {
allowed_looters[i] = who->CastToClient()->CharacterID();
2013-02-16 16:14:39 -08:00
break;
}
}
}
void Corpse::LoadPlayerCorpseDecayTime(uint32 corpse_db_id){
if(!corpse_db_id)
2013-02-16 16:14:39 -08:00
return;
uint32 active_corpse_decay_timer = database.GetCharacterCorpseDecayTimer(corpse_db_id);
if (active_corpse_decay_timer > 0 && RuleI(Character, CorpseDecayTimeMS) > (active_corpse_decay_timer * 1000)) {
corpse_decay_timer.SetTimer(RuleI(Character, CorpseDecayTimeMS) - (active_corpse_decay_timer * 1000));
}
else {
corpse_decay_timer.SetTimer(2000);
}
if (active_corpse_decay_timer > 0 && RuleI(Zone, GraveyardTimeMS) > (active_corpse_decay_timer * 1000)) {
corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS) - (active_corpse_decay_timer * 1000));
}
else {
corpse_graveyard_timer.SetTimer(3000);
2013-02-16 16:14:39 -08:00
}
}