mirror of
https://github.com/EQEmu/Server.git
synced 2026-09-04 06:16:38 +00:00
[Feature] Corpse Overhaul (#3938)
# Corpse Overhaul
Changelog:
- Player corpses now have two timers, one specific to the rezability of the corpse and the other to cover the overall rot timer of the player corpse.
- The rezability timer is based on the online presence of the player/account and is not affected by being offline.
- The rot timer is not affected by offline/online status and will count to the rot status of the corpse.
- Corpses can be rezzed multiple times, however only the first rez that yeilds returned xp will be counted. Not other rez will return any xp. This allows for a "Poor mans COTH" as was used many times in the early eras.
- All Corpse class private/protected member variables are all now prefixed with m_
- Added Corpses logging category along with many debug logs
- Removed LoadCharacterCorpseData
- Removed LoadCharacterCorpseEntity
- Added LoadCharacterCorpse(const CharacterCorpsesRepository::CharacterCorpses, const glm::vec4 &position) which simplifies areas of consumption and reduces double queries from removing LoadCharacterCorpseData and replacing LoadCharacterCorpseEntity
- All parameters that were prefixed with in_ have been dropped
- Removed two queries from CheckIsOwnerOnline and have it query the world's CLE by account_id since that is how live works
- Regenerated repository character_corpses
- Cleaned up many list iterators to use range based for loops
- Rate limit Corpse::Process m_is_rezzable with a 1 second check timer
- General code cleanup
- Added a Server Up check to bury all corpses in instances to prevent lost corpses if an instance is released during server down. This facilitates player recovery via shadowrest or priests of luclin.
This PR also now fixes a long standing issue with HasItem performance in our script plugins. It is significantly faster, we will need to coordinate quest changes and comms with operators.
```lua
if ($client->HasItemOnCorpse($item_id)) {
return 1;
}
```
```lua
--corpse
if self:HasItemOnCorpse(itemid) then
return true
end
```
Testing Completed:
- Create a Corpse
- Standard rezzing
- Ghetto Coth (No Extra XP)
- Rezzing after graveyard move
- Divine Rez works as intended
- No XP Rez (Corpse Call) does not give XP
- Corpse Burying
- Cross Instance Graveyard Corpse movement/Rezzing
- DZ End/Quit Corpse Movement/Rezzing
- Server Shutdown/Reinit DZ Corpse Movement/Rezzing
---------
Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
+212
-128
@@ -1,27 +1,10 @@
|
||||
/* EQEMu: Everquest Server Emulator
|
||||
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
||||
are required to give you total support for your newly bought product;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef CORPSE_H
|
||||
#define CORPSE_H
|
||||
|
||||
#include "mob.h"
|
||||
#include "client.h"
|
||||
#include "../common/loot.h"
|
||||
#include "../common/repositories/character_corpses_repository.h"
|
||||
|
||||
class EQApplicationPacket;
|
||||
class Group;
|
||||
@@ -31,73 +14,148 @@ class Raid;
|
||||
struct ExtraAttackOptions;
|
||||
struct NPCType;
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace EQ {
|
||||
class ItemInstance;
|
||||
}
|
||||
|
||||
#define MAX_LOOTERS 72
|
||||
|
||||
class Corpse : public Mob {
|
||||
public:
|
||||
public:
|
||||
|
||||
static void SendEndLootErrorPacket(Client* client);
|
||||
static void SendLootReqErrorPacket(Client* client, LootResponse response = LootResponse::NotAtThisTime);
|
||||
static void SendEndLootErrorPacket(Client *client);
|
||||
static void SendLootReqErrorPacket(Client *client, LootResponse response = LootResponse::NotAtThisTime);
|
||||
|
||||
Corpse(NPC* in_npc, LootItems* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime = 600000);
|
||||
Corpse(Client* client, int32 in_rezexp);
|
||||
Corpse(uint32 in_corpseid, uint32 in_charid, const char* in_charname, LootItems* 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 = false);
|
||||
Corpse(
|
||||
NPC *npc,
|
||||
LootItems *item_list,
|
||||
uint32 npc_type_id,
|
||||
const NPCType **npc_type_data,
|
||||
uint32 decay_time = 600000
|
||||
);
|
||||
Corpse(Client *c, int32 rez_exp, KilledByTypes killed_by = KilledByTypes::Killed_NPC);
|
||||
Corpse(
|
||||
uint32 corpse_id,
|
||||
uint32 character_id,
|
||||
const char *character_name,
|
||||
LootItems *item_list,
|
||||
uint32 copper,
|
||||
uint32 silver,
|
||||
uint32 gold,
|
||||
uint32 platinum,
|
||||
const glm::vec4 &position,
|
||||
float size,
|
||||
uint8 gender,
|
||||
uint16 race,
|
||||
uint8 class_,
|
||||
uint8 deity,
|
||||
uint8 level,
|
||||
uint8 texture,
|
||||
uint8 helm_texture,
|
||||
uint32 rez_exp,
|
||||
uint32 gm_rez_exp,
|
||||
KilledByTypes killed_by,
|
||||
bool is_rezzable,
|
||||
uint32 rez_remaining_time,
|
||||
bool was_at_graveyard = false
|
||||
);
|
||||
|
||||
~Corpse();
|
||||
static 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);
|
||||
|
||||
/* Corpse: General */
|
||||
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) { return true; }
|
||||
virtual void Damage(Mob* from, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; }
|
||||
bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true,
|
||||
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) override {
|
||||
virtual bool Death(
|
||||
Mob *killer_mob,
|
||||
int64 damage,
|
||||
uint16 spell_id,
|
||||
EQ::skills::SkillType attack_skill,
|
||||
KilledByTypes killed_by = KilledByTypes::Killed_NPC
|
||||
) { return true; }
|
||||
|
||||
virtual void Damage(
|
||||
Mob *from,
|
||||
int64 damage,
|
||||
uint16 spell_id,
|
||||
EQ::skills::SkillType attack_skill,
|
||||
bool avoidable = true,
|
||||
int8 buffslot = -1,
|
||||
bool iBuffTic = false,
|
||||
eSpecialAttacks special = eSpecialAttacks::None
|
||||
) { }
|
||||
|
||||
bool Attack(
|
||||
Mob *other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true,
|
||||
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr
|
||||
) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool HasRaid() { return false; }
|
||||
virtual bool HasGroup() { return false; }
|
||||
virtual Raid* GetRaid() { return 0; }
|
||||
virtual Group* GetGroup() { return 0; }
|
||||
inline uint32 GetCorpseDBID() { return corpse_db_id; }
|
||||
inline char* GetOwnerName() { return corpse_name; }
|
||||
bool IsEmpty() const;
|
||||
bool IsCorpse() const { return true; }
|
||||
bool IsPlayerCorpse() const { return is_player_corpse; }
|
||||
bool IsNPCCorpse() const { return !is_player_corpse; }
|
||||
bool IsBecomeNPCCorpse() const { return become_npc; }
|
||||
virtual void DepopNPCCorpse();
|
||||
virtual void DepopPlayerCorpse();
|
||||
bool Process();
|
||||
bool Save();
|
||||
uint32 GetCharID() { return char_id; }
|
||||
uint32 SetCharID(uint32 iCharID) { if (IsPlayerCorpse()) { return (char_id = iCharID); } return 0xFFFFFFFF; };
|
||||
uint32 GetDecayTime() { if (!corpse_decay_timer.Enabled()) return 0xFFFFFFFF; else return corpse_decay_timer.GetRemainingTime(); }
|
||||
uint32 GetRezTime() { if (!corpse_rez_timer.Enabled()) return 0; else return corpse_rez_timer.GetRemainingTime(); }
|
||||
void ResetDecayTimer();
|
||||
void SetDecayTimer(uint32 decay_time);
|
||||
void SetConsentGroupID(uint32 group_id) { if (IsPlayerCorpse()) { consented_group_id = group_id; } }
|
||||
void SetConsentRaidID(uint32 raid_id) { if (IsPlayerCorpse()) { consented_raid_id = raid_id; } }
|
||||
void SetConsentGuildID(uint32 guild_id) { if (IsPlayerCorpse()) { consented_guild_id = guild_id; } }
|
||||
void AddConsentName(std::string consent_player_name);
|
||||
void RemoveConsentName(std::string consent_player_name);
|
||||
void SendWorldSpawnPlayerCorpseInZone(uint32_t zone_id);
|
||||
bool MovePlayerCorpseToGraveyard();
|
||||
bool MovePlayerCorpseToNonInstance();
|
||||
|
||||
void Delete();
|
||||
void Bury();
|
||||
void CalcCorpseName();
|
||||
void LoadPlayerCorpseDecayTime(uint32 dbid);
|
||||
virtual bool HasRaid() { return false; }
|
||||
virtual bool HasGroup() { return false; }
|
||||
virtual Raid *GetRaid() { return nullptr; }
|
||||
virtual Group *GetGroup() { return nullptr; }
|
||||
inline uint32 GetCorpseDBID() { return m_corpse_db_id; }
|
||||
inline char *GetOwnerName() { return corpse_name; }
|
||||
bool IsEmpty() const;
|
||||
bool IsCorpse() const { return true; }
|
||||
bool IsPlayerCorpse() const { return m_is_player_corpse; }
|
||||
bool IsNPCCorpse() const { return !m_is_player_corpse; }
|
||||
bool IsBecomeNPCCorpse() const { return m_become_npc; }
|
||||
virtual void DepopNPCCorpse();
|
||||
virtual void DepopPlayerCorpse();
|
||||
bool Process();
|
||||
bool Save();
|
||||
|
||||
uint32 GetCharID() { return m_character_id; }
|
||||
|
||||
uint32 SetCharID(uint32 iCharID)
|
||||
{
|
||||
if (IsPlayerCorpse()) {
|
||||
return (m_character_id = iCharID);
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
};
|
||||
|
||||
uint32 GetDecayTime()
|
||||
{
|
||||
if (!m_corpse_decay_timer.Enabled()) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
else {
|
||||
return m_corpse_decay_timer.GetRemainingTime();
|
||||
}
|
||||
}
|
||||
uint32 GetRezTime()
|
||||
{
|
||||
if (!m_corpse_rezzable_timer.Enabled()) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return m_corpse_rezzable_timer.GetRemainingTime();
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDecayTimer();
|
||||
void SetDecayTimer(uint32 decay_time);
|
||||
void SetConsentGroupID(uint32 group_id) { if (IsPlayerCorpse()) { m_consented_group_id = group_id; }}
|
||||
void SetConsentRaidID(uint32 raid_id) { if (IsPlayerCorpse()) { m_consented_raid_id = raid_id; }}
|
||||
void SetConsentGuildID(uint32 guild_id) { if (IsPlayerCorpse()) { m_consented_guild_id = guild_id; }}
|
||||
void AddConsentName(const std::string& consent_player_name);
|
||||
void RemoveConsentName(const std::string& consent_player_name);
|
||||
void SendWorldSpawnPlayerCorpseInZone(uint32_t zone_id);
|
||||
bool MovePlayerCorpseToGraveyard();
|
||||
bool MovePlayerCorpseToNonInstance();
|
||||
|
||||
void Delete();
|
||||
void Bury();
|
||||
void CalcCorpseName();
|
||||
void LoadPlayerCorpseDecayTime(uint32 dbid);
|
||||
|
||||
/* Corpse: Items */
|
||||
uint32 GetWornItem(int16 equipSlot) const;
|
||||
uint32 GetWornItem(int16 equip_slot) const;
|
||||
LootItem *GetItem(uint16 lootslot, LootItem **bag_item_data = 0);
|
||||
void SetPlayerKillItemID(int32 pk_item_id) { player_kill_item = pk_item_id; }
|
||||
int32 GetPlayerKillItem() { return player_kill_item; }
|
||||
void SetPlayerKillItemID(int32 pk_item_id) { m_player_kill_item = pk_item_id; }
|
||||
int32 GetPlayerKillItem() { return m_player_kill_item; }
|
||||
void RemoveItem(uint16 lootslot);
|
||||
void RemoveItem(LootItem *item_data);
|
||||
void RemoveItemByID(uint32 item_id, int quantity = 1);
|
||||
@@ -121,84 +179,110 @@ class Corpse : public Mob {
|
||||
/* Corpse: Coin */
|
||||
void SetCash(uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum);
|
||||
void RemoveCash();
|
||||
uint32 GetCopper() { return copper; }
|
||||
uint32 GetSilver() { return silver; }
|
||||
uint32 GetGold() { return gold; }
|
||||
uint32 GetPlatinum() { return platinum; }
|
||||
uint32 GetCopper() { return m_copper; }
|
||||
uint32 GetSilver() { return m_silver; }
|
||||
uint32 GetGold() { return m_gold; }
|
||||
uint32 GetPlatinum() { return m_platinum; }
|
||||
|
||||
/* Corpse: Resurrection */
|
||||
bool IsRezzed() { return rez; }
|
||||
void IsRezzed(bool in_rez) { rez = in_rez; }
|
||||
void CastRezz(uint16 spellid, Mob* Caster);
|
||||
void CompleteResurrection();
|
||||
bool IsRezzed() { return m_rez; }
|
||||
void IsRezzed(bool in_rez) { m_rez = in_rez; }
|
||||
void CastRezz(uint16 spell_id, Mob *caster);
|
||||
void CompleteResurrection(bool timer_expired = false);
|
||||
bool IsRezzable() { return m_is_rezzable; }
|
||||
void SetRezTimer(bool initial_timer = false);
|
||||
|
||||
/* Corpse: Loot */
|
||||
void QueryLoot(Client* to);
|
||||
bool HasItem(uint32 item_id);
|
||||
uint16 CountItem(uint32 item_id);
|
||||
uint32 GetItemIDBySlot(uint16 loot_slot);
|
||||
uint16 GetFirstLootSlotByItemID(uint32 item_id);
|
||||
void QueryLoot(Client *to);
|
||||
bool HasItem(uint32 item_id);
|
||||
uint16 CountItem(uint32 item_id);
|
||||
uint32 GetItemIDBySlot(uint16 loot_slot);
|
||||
uint16 GetFirstLootSlotByItemID(uint32 item_id);
|
||||
std::vector<int> GetLootList();
|
||||
void LootCorpseItem(Client* client, const EQApplicationPacket* app);
|
||||
void EndLoot(Client* client, const EQApplicationPacket* app);
|
||||
void MakeLootRequestPackets(Client* client, const EQApplicationPacket* app);
|
||||
void AllowPlayerLoot(Mob *them, uint8 slot);
|
||||
void AddLooter(Mob *who);
|
||||
uint32 CountItems();
|
||||
bool CanPlayerLoot(int charid);
|
||||
void LootCorpseItem(Client *c, const EQApplicationPacket *app);
|
||||
void EndLoot(Client *c, const EQApplicationPacket *app);
|
||||
void MakeLootRequestPackets(Client *c, const EQApplicationPacket *app);
|
||||
void AllowPlayerLoot(Mob *them, uint8 slot);
|
||||
void AddLooter(Mob *who);
|
||||
uint32 CountItems();
|
||||
bool CanPlayerLoot(int character_id);
|
||||
|
||||
inline void Lock() { is_locked = true; }
|
||||
inline void UnLock() { is_locked = false; }
|
||||
inline bool IsLocked() { return is_locked; }
|
||||
inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; loot_request_type = LootRequestType::Forbidden; }
|
||||
inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); }
|
||||
inline bool IsBeingLootedBy(Client *c) { return being_looted_by == c->GetID(); }
|
||||
inline void Lock() { m_is_locked = true; }
|
||||
inline void UnLock() { m_is_locked = false; }
|
||||
inline bool IsLocked() { return m_is_locked; }
|
||||
inline void ResetLooter()
|
||||
{
|
||||
m_being_looted_by_entity_id = 0xFFFFFFFF;
|
||||
m_loot_request_type = LootRequestType::Forbidden;
|
||||
}
|
||||
inline bool IsBeingLooted() { return (m_being_looted_by_entity_id != 0xFFFFFFFF); }
|
||||
inline bool IsBeingLootedBy(Client *c) { return m_being_looted_by_entity_id == c->GetID(); }
|
||||
|
||||
/* Mob */
|
||||
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
||||
bool Summon(Client* client, bool spell, bool CheckDistance);
|
||||
void FillSpawnStruct(NewSpawn_Struct *ns, Mob *ForWho);
|
||||
bool Summon(Client *c, bool spell, bool CheckDistance);
|
||||
void Spawn();
|
||||
|
||||
char corpse_name[64];
|
||||
uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const;
|
||||
uint32 GetEquipmentColor(uint8 material_slot) const;
|
||||
inline int GetRezExp() { return rez_experience; }
|
||||
char corpse_name[64];
|
||||
uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const;
|
||||
uint32 GetEquipmentColor(uint8 material_slot) const;
|
||||
inline int64 GetRezExp() { return m_rezzed_experience; }
|
||||
inline int64 GetGMRezExp() { return m_gm_rezzed_experience; }
|
||||
uint8 GetKilledBy() { return m_killed_by_type; }
|
||||
uint32 GetRemainingRezTime() { return m_remaining_rez_time; }
|
||||
|
||||
virtual void UpdateEquipmentLight();
|
||||
|
||||
void CheckIsOwnerOnline();
|
||||
void SetOwnerOnline(bool value) { m_is_owner_online = value; }
|
||||
bool GetOwnerOnline() { return m_is_owner_online; }
|
||||
|
||||
static Corpse *LoadCharacterCorpse(
|
||||
const CharacterCorpsesRepository::CharacterCorpses &cc,
|
||||
const glm::vec4 &position
|
||||
);
|
||||
|
||||
protected:
|
||||
void MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equipSlot, std::list<uint32> &removedList);
|
||||
|
||||
private:
|
||||
bool is_player_corpse; /* Determines if Player Corpse or not */
|
||||
bool is_corpse_changed; /* Determines if corpse has changed or not */
|
||||
bool is_locked; /* Determines if corpse is locked */
|
||||
int32 player_kill_item; /* Determines if Player Kill Item */
|
||||
uint32 corpse_db_id; /* Corpse Database ID (Player Corpse) */
|
||||
uint32 char_id; /* Character ID */
|
||||
uint32 consented_group_id = 0;
|
||||
uint32 consented_raid_id = 0;
|
||||
uint32 consented_guild_id = 0;
|
||||
LootItems itemlist; /* Internal Item list used for corpses */
|
||||
uint32 copper;
|
||||
uint32 silver;
|
||||
uint32 gold;
|
||||
uint32 platinum;
|
||||
bool player_corpse_depop; /* Sets up Corpse::Process to depop the player corpse */
|
||||
uint32 being_looted_by; /* Determines what the corpse is being looted by internally for logic */
|
||||
uint32 rez_experience; /* Amount of experience that the corpse would rez for */
|
||||
bool rez;
|
||||
bool become_npc;
|
||||
int allowed_looters[MAX_LOOTERS]; /* People allowed to loot the corpse, character id */
|
||||
Timer corpse_decay_timer; /* The amount of time in millseconds in which a corpse will take to decay (Depop/Poof) */
|
||||
Timer corpse_rez_timer; /* The amount of time in millseconds in which a corpse can be rezzed */
|
||||
Timer corpse_delay_timer;
|
||||
Timer corpse_graveyard_timer;
|
||||
Timer loot_cooldown_timer; /* Delay between loot actions on the corpse entity */
|
||||
EQ::TintProfile item_tint;
|
||||
std::vector<std::string> consented_player_names;
|
||||
|
||||
LootRequestType loot_request_type;
|
||||
bool m_is_player_corpse;
|
||||
bool m_is_corpse_changed;
|
||||
bool m_is_locked;
|
||||
int32 m_player_kill_item;
|
||||
uint32 m_corpse_db_id;
|
||||
uint32 m_character_id;
|
||||
uint32 m_consented_group_id = 0;
|
||||
uint32 m_consented_raid_id = 0;
|
||||
uint32 m_consented_guild_id = 0;
|
||||
LootItems m_item_list;
|
||||
uint32 m_copper;
|
||||
uint32 m_silver;
|
||||
uint32 m_gold;
|
||||
uint32 m_platinum;
|
||||
bool m_player_corpse_depop;
|
||||
uint32 m_being_looted_by_entity_id;
|
||||
uint64 m_rezzed_experience;
|
||||
uint64 m_gm_rezzed_experience;
|
||||
uint64 m_gm_exp;
|
||||
bool m_rez;
|
||||
bool m_become_npc;
|
||||
int m_allowed_looters[MAX_LOOTERS];
|
||||
Timer m_corpse_decay_timer;
|
||||
Timer m_corpse_rezzable_timer;
|
||||
Timer m_corpse_delay_timer;
|
||||
Timer m_corpse_graveyard_timer;
|
||||
Timer m_loot_cooldown_timer;
|
||||
Timer m_check_owner_online_timer;
|
||||
Timer m_check_rezzable_timer;
|
||||
uint8 m_killed_by_type;
|
||||
bool m_is_rezzable;
|
||||
EQ::TintProfile m_item_tint;
|
||||
uint32 m_remaining_rez_time;
|
||||
bool m_is_owner_online;
|
||||
std::vector<std::string> m_consented_player_names;
|
||||
LootRequestType m_loot_request_type;
|
||||
uint32 m_account_id;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user