[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:
Fryguy 2024-02-07 23:02:30 -05:00 committed by GitHub
parent 331e04fbf8
commit 772fed5e30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1783 additions and 1289 deletions

View File

@ -166,6 +166,7 @@ public:
void GetCharactersInInstance(uint16 instance_id, std::list<uint32> &character_ids);
void PurgeExpiredInstances();
void SetInstanceDuration(uint16 instance_id, uint32 new_duration);
void CleanupInstanceCorpses();
/* Adventure related. */

View File

@ -5270,6 +5270,20 @@ MODIFY COLUMN `level` tinyint(3) UNSIGNED NOT NULL FIRST,
MODIFY COLUMN `class` tinyint(2) UNSIGNED NOT NULL AFTER `level`;
)",
.content_schema_update = true
},
ManifestEntry{
.version = 9259,
.description = "2024_01_13_corpse_rez_overhaul.sql",
.check = "SHOW COLUMNS FROM `character_corpses` LIKE 'rez_time'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `character_corpses`
ADD COLUMN `rez_time` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `wc_9`,
ADD COLUMN `gm_exp` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `rez_time`,
ADD COLUMN `killed_by` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `gm_exp`,
ADD COLUMN `rezzable` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `killed_by`;
)"
}
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{

View File

@ -570,3 +570,23 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
InstanceListRepository::UpdateOne(*this, i);
}
void Database::CleanupInstanceCorpses() {
auto l = InstanceListRepository::GetWhere(
*this,
"never_expires = 0"
);
if (l.empty()) {
return;
}
std::vector<std::string> instance_ids;
for (const auto& e : l) {
instance_ids.emplace_back(std::to_string(e.id));
}
const auto imploded_instance_ids = Strings::Implode(",", instance_ids);
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
}

View File

@ -140,6 +140,7 @@ namespace Logs {
DataBuckets,
Zoning,
EqTime,
Corpses,
MaxCategoryID /* Don't Remove this */
};
@ -239,6 +240,7 @@ namespace Logs {
"DataBuckets",
"Zoning",
"EqTime",
"Corpses",
};
}

View File

@ -824,6 +824,16 @@
OutF(LogSys, Logs::Detail, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogCorpses(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Corpses))\
OutF(LogSys, Logs::General, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogCorpsesDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Corpses))\
OutF(LogSys, Logs::Detail, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -66,6 +66,10 @@ public:
uint32_t wc_7;
uint32_t wc_8;
uint32_t wc_9;
uint32_t rez_time;
uint32_t gm_exp;
uint32_t killed_by;
uint8_t rezzable;
};
static std::string PrimaryKey()
@ -123,6 +127,10 @@ public:
"wc_7",
"wc_8",
"wc_9",
"rez_time",
"gm_exp",
"killed_by",
"rezzable",
};
}
@ -176,6 +184,10 @@ public:
"wc_7",
"wc_8",
"wc_9",
"rez_time",
"gm_exp",
"killed_by",
"rezzable",
};
}
@ -263,6 +275,10 @@ public:
e.wc_7 = 0;
e.wc_8 = 0;
e.wc_9 = 0;
e.rez_time = 0;
e.gm_exp = 0;
e.killed_by = 0;
e.rezzable = 0;
return e;
}
@ -346,6 +362,10 @@ public:
e.wc_7 = row[44] ? static_cast<uint32_t>(strtoul(row[44], nullptr, 10)) : 0;
e.wc_8 = row[45] ? static_cast<uint32_t>(strtoul(row[45], nullptr, 10)) : 0;
e.wc_9 = row[46] ? static_cast<uint32_t>(strtoul(row[46], nullptr, 10)) : 0;
e.rez_time = row[47] ? static_cast<uint32_t>(strtoul(row[47], nullptr, 10)) : 0;
e.gm_exp = row[48] ? static_cast<uint32_t>(strtoul(row[48], nullptr, 10)) : 0;
e.killed_by = row[49] ? static_cast<uint32_t>(strtoul(row[49], nullptr, 10)) : 0;
e.rezzable = row[50] ? static_cast<uint8_t>(strtoul(row[50], nullptr, 10)) : 0;
return e;
}
@ -425,6 +445,10 @@ public:
v.push_back(columns[44] + " = " + std::to_string(e.wc_7));
v.push_back(columns[45] + " = " + std::to_string(e.wc_8));
v.push_back(columns[46] + " = " + std::to_string(e.wc_9));
v.push_back(columns[47] + " = " + std::to_string(e.rez_time));
v.push_back(columns[48] + " = " + std::to_string(e.gm_exp));
v.push_back(columns[49] + " = " + std::to_string(e.killed_by));
v.push_back(columns[50] + " = " + std::to_string(e.rezzable));
auto results = db.QueryDatabase(
fmt::format(
@ -493,6 +517,10 @@ public:
v.push_back(std::to_string(e.wc_7));
v.push_back(std::to_string(e.wc_8));
v.push_back(std::to_string(e.wc_9));
v.push_back(std::to_string(e.rez_time));
v.push_back(std::to_string(e.gm_exp));
v.push_back(std::to_string(e.killed_by));
v.push_back(std::to_string(e.rezzable));
auto results = db.QueryDatabase(
fmt::format(
@ -569,6 +597,10 @@ public:
v.push_back(std::to_string(e.wc_7));
v.push_back(std::to_string(e.wc_8));
v.push_back(std::to_string(e.wc_9));
v.push_back(std::to_string(e.rez_time));
v.push_back(std::to_string(e.gm_exp));
v.push_back(std::to_string(e.killed_by));
v.push_back(std::to_string(e.rezzable));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@ -649,6 +681,10 @@ public:
e.wc_7 = row[44] ? static_cast<uint32_t>(strtoul(row[44], nullptr, 10)) : 0;
e.wc_8 = row[45] ? static_cast<uint32_t>(strtoul(row[45], nullptr, 10)) : 0;
e.wc_9 = row[46] ? static_cast<uint32_t>(strtoul(row[46], nullptr, 10)) : 0;
e.rez_time = row[47] ? static_cast<uint32_t>(strtoul(row[47], nullptr, 10)) : 0;
e.gm_exp = row[48] ? static_cast<uint32_t>(strtoul(row[48], nullptr, 10)) : 0;
e.killed_by = row[49] ? static_cast<uint32_t>(strtoul(row[49], nullptr, 10)) : 0;
e.rezzable = row[50] ? static_cast<uint8_t>(strtoul(row[50], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@ -720,6 +756,10 @@ public:
e.wc_7 = row[44] ? static_cast<uint32_t>(strtoul(row[44], nullptr, 10)) : 0;
e.wc_8 = row[45] ? static_cast<uint32_t>(strtoul(row[45], nullptr, 10)) : 0;
e.wc_9 = row[46] ? static_cast<uint32_t>(strtoul(row[46], nullptr, 10)) : 0;
e.rez_time = row[47] ? static_cast<uint32_t>(strtoul(row[47], nullptr, 10)) : 0;
e.gm_exp = row[48] ? static_cast<uint32_t>(strtoul(row[48], nullptr, 10)) : 0;
e.killed_by = row[49] ? static_cast<uint32_t>(strtoul(row[49], nullptr, 10)) : 0;
e.rezzable = row[50] ? static_cast<uint8_t>(strtoul(row[50], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@ -841,6 +881,10 @@ public:
v.push_back(std::to_string(e.wc_7));
v.push_back(std::to_string(e.wc_8));
v.push_back(std::to_string(e.wc_9));
v.push_back(std::to_string(e.rez_time));
v.push_back(std::to_string(e.gm_exp));
v.push_back(std::to_string(e.killed_by));
v.push_back(std::to_string(e.rezzable));
auto results = db.QueryDatabase(
fmt::format(
@ -910,6 +954,10 @@ public:
v.push_back(std::to_string(e.wc_7));
v.push_back(std::to_string(e.wc_8));
v.push_back(std::to_string(e.wc_9));
v.push_back(std::to_string(e.rez_time));
v.push_back(std::to_string(e.gm_exp));
v.push_back(std::to_string(e.killed_by));
v.push_back(std::to_string(e.rezzable));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}

View File

@ -65,7 +65,7 @@ public:
fmt::format(
"UPDATE `{}` SET `is_buried` = 1 WHERE `is_buried` = 0 AND (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time_of_death)) > {} AND time_of_death != 0",
TableName(),
RuleI(Character, CorpseDecayTimeMS) / 1000
RuleI(Character, CorpseDecayTime) / 1000
)
);

View File

@ -47,8 +47,11 @@ RULE_INT(Character, DeathExpLossMultiplier, 3, "Adjust how much experience is lo
RULE_BOOL(Character, DeathKeepLevel, false, "Players can not drop below 0% experience from death.")
RULE_BOOL(Character, UseDeathExpLossMult, false, "Setting to control whether DeathExpLossMultiplier or the code default is used: (Level x Level / 18.0) x 12000")
RULE_BOOL(Character, UseOldRaceRezEffects, false, "Older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore")
RULE_INT(Character, CorpseDecayTimeMS, 10800000, "Time after which the corpse decays (milliseconds)")
RULE_INT(Character, CorpseResTimeMS, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds)")
RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse decays (milliseconds) DEFAULT: 604800000 (7 Days)")
RULE_INT( Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT( Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
RULE_INT( Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
@ -580,10 +583,10 @@ RULE_REAL(Combat, BashACBonusDivisor, 25.0, "this divides the AC value contribut
RULE_CATEGORY_END()
RULE_CATEGORY(NPC)
RULE_INT(NPC, MinorNPCCorpseDecayTimeMS, 450000, "NPC corpse decay time, if NPC below level 55 (milliseconds)")
RULE_INT(NPC, MajorNPCCorpseDecayTimeMS, 1500000, "NPC corpse decay time, if NPC equal or greater than level 55 (milliseconds)")
RULE_INT(NPC, MinorNPCCorpseDecayTime, 450000, "NPC corpse decay time, if NPC below level 55 (milliseconds)")
RULE_INT(NPC, MajorNPCCorpseDecayTime, 1500000, "NPC corpse decay time, if NPC equal or greater than level 55 (milliseconds)")
RULE_INT(NPC, CorpseUnlockTimer, 150000, "Time after which corpses are unlocked for everyone to loot (milliseconds)")
RULE_INT(NPC, EmptyNPCCorpseDecayTimeMS, 0, "NPC corpse decay time, if no items are left on the corpse (milliseconds)")
RULE_INT(NPC, EmptyNPCCorpseDecayTime, 0, "NPC corpse decay time, if no items are left on the corpse (milliseconds)")
RULE_BOOL(NPC, UseItemBonusesForNonPets, true, "Switch whether item bonuses should be used for NPCs who are not pets")
RULE_BOOL(NPC, UseBaneDamage, false, "If NPCs can't inherently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs")
RULE_INT(NPC, SayPauseTimeInSec, 5, "Time span in which an NPC pauses his movement after a Say event without aggro (seconds)")

View File

@ -84,6 +84,7 @@
#define ServerOP_UpdateSpawn 0x003f
#define ServerOP_SpawnStatusChange 0x0040
#define ServerOP_DropClient 0x0041 // DropClient
#define ServerOP_IsOwnerOnline 0x0042
#define ServerOP_DepopAllPlayersCorpses 0x0060
#define ServerOP_QGlobalUpdate 0x0061
#define ServerOP_QGlobalDelete 0x0062
@ -1653,6 +1654,14 @@ struct ServerRequestTellQueue_Struct {
char name[64];
};
struct ServerIsOwnerOnline_Struct {
char name[64];
uint32 corpse_id;
uint16 zone_id;
uint8 online;
uint32 account_id;
};
struct UCSServerStatus_Struct {
uint8 available; // non-zero=true, 0=false
union {

View File

@ -1495,7 +1495,7 @@ int SharedDatabase::DeleteStalePlayerCorpses() {
*this,
fmt::format(
"(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time_of_death)) > {} AND time_of_death != 0",
RuleI(Character, CorpseDecayTimeMS) / 1000
RuleI(Character, CorpseDecayTime) / 1000
)
)
);

View File

@ -212,6 +212,7 @@
#define SPELL_ANCIENT_CRY_OF_CHAOS 5032
#define SPELL_BLOODTHIRST 8476
#define SPELL_AMPLIFICATION 2603
#define SPELL_DIVINE_REZ 2738
// discipline IDs.
#define DISC_UNHOLY_AURA 4520

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9258
#define CURRENT_BINARY_DATABASE_VERSION 9259
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9042

View File

@ -449,8 +449,12 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
->LoadTaskData()
->LoadSharedTaskState();
LogInfo("Purging expired shared tasks");
shared_task_manager.PurgeExpiredSharedTasks();
LogInfo("Cleaning up instance corpses");
database.CleanupInstanceCorpses();
return true;
}

View File

@ -1427,6 +1427,31 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
RuleManager::Instance()->LoadRules(&database, "default", true);
break;
}
case ServerOP_IsOwnerOnline: {
if (pack->size != sizeof(ServerIsOwnerOnline_Struct)) {
break;
}
auto o = (ServerIsOwnerOnline_Struct*) pack->pBuffer;
auto cle = client_list.FindCLEByAccountID(o->account_id);
o->online = cle ? 1 : 0;
if (o->online) {
LogCorpsesDetail(
"ServerOP_IsOwnerOnline account_id [{}] corpse name [{}] found to be online, sending online update to zone_id [{}]",
o->account_id,
o->name,
o->zone_id
);
}
auto zs = zoneserver_list.FindByZoneID(o->zone_id);
if (zs) {
zs->SendPacket(pack);
}
break;
}
case ServerOP_ReloadContentFlags: {
zoneserver_list.SendPacket(pack);
content_service.SetExpansionContext()->ReloadContentFlags();

View File

@ -1718,16 +1718,12 @@ void Client::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::Skill
}
}
bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill)
bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by)
{
if (!ClientFinishedLoading()) {
if (!ClientFinishedLoading() || dead) {
return false;
}
if (dead) {
return false; //cant die more than once...
}
if (!spell) {
spell = SPELL_UNKNOWN;
}
@ -1735,7 +1731,7 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
if (parse->PlayerHasQuestSub(EVENT_DEATH)) {
const auto& export_string = fmt::format(
"{} {} {} {}",
killerMob ? killerMob->GetID() : 0,
killer_mob ? killer_mob->GetID() : 0,
damage,
spell,
static_cast<int>(attack_skill)
@ -1749,7 +1745,7 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
}
}
if (killerMob && killerMob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) {
if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) {
char val1[20] = { 0 };
entity_list.MessageCloseString(
@ -1758,14 +1754,14 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
RuleI(Range, DamageMessages),
Chat::NonMelee, /* 283 */
HIT_NON_MELEE, /* %1 hit %2 for %3 points of non-melee damage. */
killerMob->GetCleanName(), /* Message1 */
killer_mob->GetCleanName(), /* Message1 */
GetCleanName(), /* Message2 */
ConvertArray(damage, val1)/* Message3 */
);
}
int exploss = 0;
LogCombat("Fatal blow dealt by [{}] with [{}] damage, spell [{}], skill [{}]", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill);
LogCombat("Fatal blow dealt by [{}] with [{}] damage, spell [{}], skill [{}]", killer_mob ? killer_mob->GetName() : "Unknown", damage, spell, attack_skill);
// #1: Send death packet to everyone
uint8 killed_level = GetLevel();
@ -1785,7 +1781,7 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
EQApplicationPacket app(OP_Death, sizeof(Death_Struct));
Death_Struct* d = (Death_Struct*)app.pBuffer;
d->spawn_id = GetID();
d->killer_id = killerMob ? killerMob->GetID() : 0;
d->killer_id = killer_mob ? killer_mob->GetID() : 0;
d->corpseid = GetID();
d->bindzoneid = m_pp.binds[0].zone_id;
d->spell_id = IsValidSpell(spell) ? spell : 0xffffffff;
@ -1812,42 +1808,45 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
GetMerc()->Suspend();
}
if (killerMob) {
if (killerMob->IsNPC()) {
if (parse->HasQuestSub(killerMob->GetNPCTypeID(), EVENT_SLAY)) {
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0);
if (killer_mob) {
if (killer_mob->IsNPC()) {
if (parse->HasQuestSub(killer_mob->GetNPCTypeID(), EVENT_SLAY)) {
parse->EventNPC(EVENT_SLAY, killer_mob->CastToNPC(), this, "", 0);
}
if (emoteid) {
killerMob->CastToNPC()->DoNPCEmote(EQ::constants::EmoteEventTypes::KilledPC, emoteid, this);
killed_by = KilledByTypes::Killed_NPC;
auto emote_id = killer_mob->GetEmoteID();
if (emote_id) {
killer_mob->CastToNPC()->DoNPCEmote(EQ::constants::EmoteEventTypes::KilledPC, emoteid, this);
}
killerMob->TrySpellOnKill(killed_level, spell);
} else if (killerMob->IsBot()) {
killer_mob->TrySpellOnKill(killed_level, spell);
} else if (killer_mob->IsBot()) {
if (parse->BotHasQuestSub(EVENT_SLAY)) {
parse->EventBot(EVENT_SLAY, killerMob->CastToBot(), this, "", 0);
parse->EventBot(EVENT_SLAY, killer_mob->CastToBot(), this, "", 0);
}
killerMob->TrySpellOnKill(killed_level, spell);
killer_mob->TrySpellOnKill(killed_level, spell);
}
if (
killerMob->IsClient() &&
(IsDueling() || killerMob->CastToClient()->IsDueling())
killer_mob->IsClient() &&
(IsDueling() || killer_mob->CastToClient()->IsDueling())
) {
SetDueling(false);
SetDuelTarget(0);
if (
killerMob->IsClient() &&
killerMob->CastToClient()->IsDueling() &&
killerMob->CastToClient()->GetDuelTarget() == GetID()
killer_mob->IsClient() &&
killer_mob->CastToClient()->IsDueling() &&
killer_mob->CastToClient()->GetDuelTarget() == GetID()
) {
//if duel opponent killed us...
killerMob->CastToClient()->SetDueling(false);
killerMob->CastToClient()->SetDuelTarget(0);
entity_list.DuelMessage(killerMob, this, false);
}
else {
killer_mob->CastToClient()->SetDueling(false);
killer_mob->CastToClient()->SetDuelTarget(0);
entity_list.DuelMessage(killer_mob, this, false);
killed_by = KilledByTypes::Killed_DUEL;
} else {
//otherwise, we just died, end the duel.
Mob* who = entity_list.GetMob(GetDuelTarget());
if (who && who->IsClient()) {
@ -1855,6 +1854,8 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
who->CastToClient()->SetDuelTarget(0);
}
}
} else if (killer_mob->IsClient()) {
killed_by = KilledByTypes::Killed_PVP;
}
}
@ -1906,12 +1907,11 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
if ((GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC()) {
exploss = 0;
} else if (killerMob) {
if (killerMob->IsClient()) {
exploss = 0;
} else if (killerMob->GetOwner() && killerMob->GetOwner()->IsClient()) {
exploss = 0;
} else if (killerMob->IsBot()) {
} else if (killer_mob) {
if (
killer_mob->IsOfClientBot() ||
(killer_mob->GetOwner() && killer_mob->GetOwner()->IsOfClientBot())
) {
exploss = 0;
}
}
@ -1964,11 +1964,11 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
RuleB(Character, LeaveNakedCorpses)
) {
// creating the corpse takes the cash/items off the player too
new_corpse = new Corpse(this, exploss);
new_corpse = new Corpse(this, exploss, killed_by);
std::string tmp;
database.GetVariable("ServerType", tmp);
if (tmp[0] == '1' && tmp[1] == '\0' && killerMob && killerMob->IsClient()) {
if (tmp[0] == '1' && tmp[1] == '\0' && killer_mob && killer_mob->IsClient()) {
database.GetVariable("PvPreward", tmp);
auto reward = Strings::ToInt(tmp);
if (reward == 3) {
@ -1986,8 +1986,8 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
new_corpse->SetPlayerKillItemID(0);
}
if (killerMob->CastToClient()->isgrouped) {
auto* group = entity_list.GetGroupByClient(killerMob->CastToClient());
if (killer_mob->CastToClient()->isgrouped) {
auto* group = entity_list.GetGroupByClient(killer_mob->CastToClient());
if (group) {
for (int i = 0; i < 6; i++) {
if (group->members[i]) {
@ -2058,15 +2058,15 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
/* QS: PlayerLogDeaths */
if (RuleB(QueryServ, PlayerLogDeaths)) {
const char * killer_name = "";
if (killerMob && killerMob->GetCleanName()) { killer_name = killerMob->GetCleanName(); }
if (killer_mob && killer_mob->GetCleanName()) { killer_name = killer_mob->GetCleanName(); }
std::string event_desc = StringFormat("Died in zoneid:%i instid:%i by '%s', spellid:%i, damage:%i", GetZoneID(), GetInstanceID(), killer_name, spell, damage);
QServ->PlayerLogEvent(Player_Log_Deaths, CharacterID(), event_desc);
}
if (player_event_logs.IsEventEnabled(PlayerEvent::DEATH)) {
auto e = PlayerEvent::DeathEvent{
.killer_id = killerMob ? static_cast<uint32>(killerMob->GetID()) : static_cast<uint32>(0),
.killer_name = killerMob ? killerMob->GetCleanName() : "No Killer",
.killer_id = killer_mob ? static_cast<uint32>(killer_mob->GetID()) : static_cast<uint32>(0),
.killer_name = killer_mob ? killer_mob->GetCleanName() : "No Killer",
.damage = damage,
.spell_id = spell,
.spell_name = IsValidSpell(spell) ? spells[spell].name : "No Spell",
@ -2080,7 +2080,7 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
if (parse->PlayerHasQuestSub(EVENT_DEATH_COMPLETE)) {
const auto& export_string = fmt::format(
"{} {} {} {}",
killerMob ? killerMob->GetID() : 0,
killer_mob ? killer_mob->GetID() : 0,
damage,
spell,
static_cast<int>(attack_skill)
@ -2092,6 +2092,7 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
}
return true;
}
//SYNC WITH: tune.cpp, mob.h TuneNPCAttack
bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts)
{
@ -2373,7 +2374,7 @@ void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillTyp
}
}
bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill)
bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by)
{
LogCombat(
"Fatal blow dealt by [{}] with [{}] damage, spell [{}], skill [{}]",
@ -2836,8 +2837,8 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
&NPCTypedata,
(
level > 54 ?
RuleI(NPC, MajorNPCCorpseDecayTimeMS) :
RuleI(NPC, MinorNPCCorpseDecayTimeMS)
RuleI(NPC, MajorNPCCorpseDecayTime) :
RuleI(NPC, MinorNPCCorpseDecayTime)
)
);

View File

@ -34,7 +34,7 @@ public:
~Beacon();
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) { return true; }
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) { return; }
virtual bool HasRaid() { return false; }
virtual bool HasGroup() { return false; }

View File

@ -435,8 +435,9 @@ Bot::Bot(
SetMana(0);
SpellOnTarget(resurrection_sickness_spell_id, this); // Rezz effects
} else {
SetHP(GetMaxHP());
SetMana(GetMaxMana());
SetHP(GetMaxHP() / 20);
SetMana(GetMaxMana() / 20);
SetEndurance(GetMaxEndurance() / 20);
}
}
@ -4480,20 +4481,24 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
}
}
bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) {
if (!NPC::Death(killerMob, damage, spell_id, attack_skill)) {
bool Bot::Death(Mob *killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by)
{
if (!NPC::Death(killer_mob, damage, spell_id, attack_skill)) {
return false;
}
Mob *my_owner = GetBotOwner();
if (my_owner && my_owner->IsClient() && my_owner->CastToClient()->GetBotOption(Client::booDeathMarquee)) {
if (killerMob)
my_owner->CastToClient()->SendMarqueeMessage(Chat::White, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName()));
else
if (killer_mob) {
my_owner->CastToClient()->SendMarqueeMessage(Chat::White, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killer_mob->GetCleanName()));
} else {
my_owner->CastToClient()->SendMarqueeMessage(Chat::White, 510, 0, 1000, 3000, StringFormat("%s has been slain", GetCleanName()));
}
}
const auto c = entity_list.GetCorpseByID(GetID());
if (c) {
c->Depop();
}
@ -4507,19 +4512,19 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill
if (parse->BotHasQuestSub(EVENT_DEATH_COMPLETE)) {
const auto& export_string = fmt::format(
"{} {} {} {}",
killerMob ? killerMob->GetID() : 0,
killer_mob ? killer_mob->GetID() : 0,
damage,
spell_id,
static_cast<int>(attack_skill)
);
parse->EventBot(EVENT_DEATH_COMPLETE, this, killerMob, export_string, 0);
parse->EventBot(EVENT_DEATH_COMPLETE, this, killer_mob, export_string, 0);
}
Zone();
entity_list.RemoveBot(GetID());
return true;
return true;
}
void Bot::Damage(Mob *from, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) {

View File

@ -131,7 +131,7 @@ public:
Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData, int32 expansion_bitmask);
//abstract virtual override function implementations requird by base abstract class
bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) override;
bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC) override;
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) override;

View File

@ -261,7 +261,7 @@ public:
bool GotoPlayerRaid(const std::string& player_name);
//abstract virtual function implementations required by base abstract class
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill);
virtual bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC);
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);
virtual bool HasRaid() { return (GetRaid() ? true : false); }
virtual bool HasGroup() { return (GetGroup() ? true : false); }
@ -1459,7 +1459,13 @@ public:
void BuryPlayerCorpses();
int64 GetCorpseCount() { return database.GetCharacterCorpseCount(CharacterID()); }
uint32 GetCorpseID(int corpse) { return database.GetCharacterCorpseID(CharacterID(), corpse); }
uint32 GetCorpseItemAt(int corpse_id, int slot_id) { return database.GetCharacterCorpseItemAt(corpse_id, slot_id); }
uint32 GetCorpseItemAt(int corpse_id, int slot_id) {
if (!corpse_id) {
return 0;
}
return database.GetCharacterCorpseItemAt(corpse_id, slot_id);
}
void SuspendMinion(int value);
void Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration);
void NotifyNewTitlesAvailable();

View File

@ -5280,28 +5280,87 @@ void Client::Handle_OP_ConsiderCorpse(const EQApplicationPacket *app)
}
}
uint32 decay_time = t->GetDecayTime();
if (decay_time) {
const std::string& time_string = Strings::SecondsToTime(decay_time, true);
Message(
Chat::NPCQuestSay,
fmt::format(
"This corpse will decay in {}.",
Strings::ToLower(time_string)
).c_str()
);
if (t->IsPlayerCorpse()) {
Message(
uint32 days, hours, minutes, seconds, remaining_time = 0;
if (t && t->IsNPCCorpse()) {
remaining_time = t->GetDecayTime();
if (remaining_time != 0) {
seconds = (remaining_time / 1000) % 60;
minutes = (remaining_time / 60000) % 60;
MessageString(
Chat::NPCQuestSay,
fmt::format(
"This corpse {} be resurrected.",
t->IsRezzed() ? "cannot" : "can"
).c_str()
CORPSE_DECAY_TIME_MINUTE,
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
} else {
MessageString(Chat::NPCQuestSay, CORPSE_DECAY_NOW);
}
} else if (t && t->IsPlayerCorpse()) {
remaining_time = t->GetRemainingRezTime();
if (!t->IsRezzed()) {
if (remaining_time > 0) {
seconds = (remaining_time / 1000) % 60;
minutes = (remaining_time / 60000) % 60;
hours = (remaining_time / 3600000) % 24;
if (hours) {
MessageString(
Chat::White,
CORPSE_REZ_TIME_HOUR,
std::to_string(hours).c_str(),
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
} else {
MessageString(
Chat::White,
CORPSE_REZ_TIME_MINUTE,
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
}
hours = 0;
} else {
MessageString(Chat::White, CORPSE_TOO_OLD);
}
} else {
Message(Chat::White, "This corpse has already accepted a resurrection.");
}
remaining_time = t->GetDecayTime();
if (remaining_time != 0) {
seconds = (remaining_time / 1000) % 60;
minutes = (remaining_time / 60000) % 60;
hours = (remaining_time / 3600000) % 24;
days = remaining_time / 86400000;
if (days) {
MessageString(
Chat::White,
CORPSE_DECAY_TIME_DAY,
std::to_string(days).c_str(),
std::to_string(hours).c_str(),
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
} else if (hours) {
MessageString(
Chat::White,
CORPSE_DECAY_TIME_HOUR,
std::to_string(hours).c_str(),
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
} else {
MessageString(
Chat::White,
CORPSE_DECAY_TIME_MINUTE,
std::to_string(minutes).c_str(),
std::to_string(seconds).c_str()
);
}
} else {
MessageString(Chat::White, CORPSE_DECAY_NOW);
}
} else {
MessageString(Chat::NPCQuestSay, CORPSE_DECAY_NOW);
}
}

View File

@ -1049,18 +1049,21 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I
RuleI(Character, OldResurrectionSicknessSpellID) :
RuleI(Character, ResurrectionSicknessSpellID)
);
SpellOnTarget(resurrection_sickness_spell_id, this); // Rezz effects
}
else {
SpellOnTarget(resurrection_sickness_spell_id, this);
} else if (SpellID == SPELL_DIVINE_REZ) {
SetHP(GetMaxHP());
SetMana(GetMaxMana());
SetEndurance(GetMaxEndurance());
} else {
SetHP(GetMaxHP() / 20);
SetMana(GetMaxMana() / 20);
SetEndurance(GetMaxEndurance() / 20);
}
if(spells[SpellID].base_value[0] < 100 && spells[SpellID].base_value[0] > 0 && PendingRezzXP > 0)
{
if(spells[SpellID].base_value[0] < 100 && spells[SpellID].base_value[0] > 0 && PendingRezzXP > 0) {
SetEXP(((int)(GetEXP()+((float)((PendingRezzXP / 100) * spells[SpellID].base_value[0])))),
GetAAXP(),true);
}
else if (spells[SpellID].base_value[0] == 100 && PendingRezzXP > 0) {
} else if (spells[SpellID].base_value[0] == 100 && PendingRezzXP > 0) {
SetEXP((GetEXP() + PendingRezzXP), GetAAXP(), true);
}

View File

@ -226,6 +226,12 @@ enum class LootRequestType : uint8 {
AllowedPVPDefined,
};
enum class KilledByTypes : uint8 {
Killed_NPC = 0,
Killed_DUEL = 1,
Killed_PVP = 2
};
namespace Journal {
enum class SpeakMode : uint8 {
Raw = 0, // this just uses the raw message

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -34,7 +34,7 @@ public:
~Encounter();
//abstract virtual function implementations required by base abstract class
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) { return true; }
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) { return; }
bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) override {

View File

@ -25,6 +25,7 @@
#include "zonedb.h"
#include "../common/events/player_event_logs.h"
#include "bot.h"
#include "../common/repositories/character_corpse_items_repository.h"
extern WorldServer worldserver;
@ -4792,26 +4793,29 @@ void Client::SummonItemIntoInventory(
bool Client::HasItemOnCorpse(uint32 item_id)
{
const uint32 corpse_count = GetCorpseCount();
if (!corpse_count) {
return EQ::invslot::SLOT_INVALID;
auto corpses = CharacterCorpsesRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
if (corpses.empty()) {
return false;
}
for (int i = 0; i < corpse_count; i++) {
const uint32 corpse_id = GetCorpseID(i);
std::vector<uint32> corpse_ids;
corpse_ids.reserve(corpses.size());
for (int16 slot_id = EQ::invslot::POSSESSIONS_BEGIN; slot_id < EQ::invslot::POSSESSIONS_END; slot_id++) {
const uint32 current_item_id = GetCorpseItemAt(corpse_id, slot_id);
if (current_item_id && current_item_id == item_id) {
return true;
}
}
for (auto &corpse : corpses) {
corpse_ids.push_back(corpse.id);
}
for (int16 slot_id = EQ::invbag::GENERAL_BAGS_BEGIN; slot_id < EQ::invbag::GENERAL_BAGS_END; slot_id++) {
const uint32 current_item_id = GetCorpseItemAt(corpse_id, slot_id);
if (current_item_id && current_item_id == item_id) {
return true;
}
auto items = CharacterCorpseItemsRepository::GetWhere(
database,
fmt::format(
"corpse_id IN ({})",
Strings::Join(corpse_ids, ",")
)
);
for (auto &item : items) {
if (item.item_id == item_id) {
return true;
}
}

View File

@ -4084,10 +4084,9 @@ Mob* Merc::GetOwnerOrSelf() {
return Result;
}
bool Merc::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill)
bool Merc::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, uint8 killed_by)
{
if(!NPC::Death(killerMob, damage, spell, attack_skill))
{
if (!NPC::Death(killer_mob, damage, spell, attack_skill)) {
return false;
}
@ -4100,8 +4099,7 @@ bool Merc::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::SkillTy
// entity_list.GetCorpseByID(GetID())->Depop();
// If client is in zone, suspend merc, else depop it.
if (!Suspend())
{
if (!Suspend()) {
Depop();
}

View File

@ -52,7 +52,7 @@ public:
virtual ~Merc();
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill);
virtual bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, uint8 killed_by = 0);
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);
virtual bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr);

View File

@ -541,7 +541,7 @@ public:
bool CanClassEquipItem(uint32 item_id);
bool CanRaceEquipItem(uint32 item_id);
bool AffectedBySpellExcludingSlot(int slot, int effect);
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) = 0;
virtual bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC) = 0;
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) = 0;
void SetHP(int64 hp);

View File

@ -130,7 +130,7 @@ public:
static NPC * SpawnZonePointNodeNPC(std::string name, const glm::vec4 &position);
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill);
virtual bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC);
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 = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) override;

View File

@ -77,6 +77,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
#include "../common/data_verification.h"
#include "../common/misc_functions.h"
#include "../common/events/player_event_logs.h"
#include "../common/repositories/character_corpses_repository.h"
#include "data_bucket.h"
#include "quest_parser_collection.h"
@ -227,7 +228,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
(
!RuleB(Spells, ManaTapsRequireNPCMana) ||
(
RuleB(Spells, ManaTapsRequireNPCMana) &&
RuleB(Spells, ManaTapsRequireNPCMana) &&
GetTarget()->GetMana() == 0
)
)
@ -3064,7 +3065,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
if (IsResurrectionEffects(spellid1)) {
return 0;
}
if (spellbonuses.CompleteHealBuffBlocker && IsEffectInSpell(spellid2, SE_CompleteHeal)) {
Message(0, "You must wait before you can be affected by this spell again.");
return -1;
@ -4128,7 +4129,7 @@ bool Mob::SpellOnTarget(
if (IsClient() && (casting_spell_aa_id == aaDireCharm || casting_spell_aa_id == aaDireCharm2 || casting_spell_aa_id == aaDireCharm3)) {
StopCasting();
}
//the above call does the message to the client if needed
LogSpells("Spell [{}] can't take hold due to immunity [{}] -> [{}]", spell_id, GetName(), spelltar->GetName());
safe_delete(action_packet);
@ -4544,44 +4545,6 @@ bool Mob::SpellOnTarget(
return true;
}
void Corpse::CastRezz(uint16 spellid, Mob* Caster)
{
LogSpells("Corpse::CastRezz spellid [{}], Rezzed() is [{}], rezzexp is [{}]", spellid,IsRezzed(),rez_experience);
if(IsRezzed()){
if(Caster && Caster->IsClient())
Caster->Message(Chat::Red,"This character has already been resurrected.");
return;
}
/*
if(!can_rez) {
if(Caster && Caster->IsClient())
Caster->MessageString(Chat::White, CORPSE_TOO_OLD);
return;
}
*/
auto outapp = new EQApplicationPacket(OP_RezzRequest, sizeof(Resurrect_Struct));
Resurrect_Struct* rezz = (Resurrect_Struct*) outapp->pBuffer;
// Why are we truncating these names to 30 characters ?
memcpy(rezz->your_name,corpse_name,30);
memcpy(rezz->corpse_name,name,30);
memcpy(rezz->rezzer_name,Caster->GetName(),30);
rezz->zone_id = zone->GetZoneID();
rezz->instance_id = zone->GetInstanceID();
rezz->spellid = spellid;
rezz->x = m_Position.x;
rezz->y = m_Position.y;
rezz->z = GetFixedZ(m_Position);
rezz->unknown000 = 0x00000000;
rezz->unknown020 = 0x00000000;
rezz->unknown088 = 0x00000000;
// We send this to world, because it needs to go to the player who may not be in this zone.
worldserver.RezzPlayer(outapp, rez_experience, corpse_db_id, OP_RezzRequest);
safe_delete(outapp);
}
std::vector<uint16> Mob::GetBuffSpellIDs()
{
std::vector<uint16> l;
@ -5012,7 +4975,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
caster->MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name);
return true;
}
caster->MessageString(Chat::Red, CANNOT_CHARM_YET); // need to verify message type, not in MQ2Cast for easy look up<Paste>
AddToHateList(caster, 1,0,true,false,false,spell_id);
return true;
@ -5191,7 +5154,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if (IsHarmTouchSpell(spell_id) && caster->IsClient() && caster->CastToClient()->FindBuff(DISC_UNHOLY_AURA)) {
resist_type = RESIST_DISEASE;
}
//Get the resist chance for the target
if (resist_type == RESIST_NONE || spells[spell_id].no_resist) {
LogSpells("Spell was unresistable");

View File

@ -171,7 +171,11 @@
#define PET_REPORT_HP 488 //I have %1 percent of my hit points left.
#define PET_NO_TAUNT 489 //No longer taunting attackers, Master.
#define PET_DO_TAUNT 490 //Taunting attackers as normal, Master.
#define CORPSE_DECAY1 495 //This corpse will decay in %1 minute(s) %2 seconds.
#define CORPSE_REZ_TIME_HOUR 491 //This corpse's resurrection time will expire in %1 hour(s) %2 minute(s) %3 seconds.
#define CORPSE_REZ_TIME_MINUTE 492 //This corpse's resurrection time will expire in %1 minute(s) %2 seconds.
#define CORPSE_DECAY_TIME_DAY 493 //This corpse will decay in %1 day(s) %2 hour(s) %3 minute(s) %4 seconds.
#define CORPSE_DECAY_TIME_HOUR 494 //This corpse will decay in %1 hour(s) %2 minute(s) %3 seconds.
#define CORPSE_DECAY_TIME_MINUTE 495 //This corpse will decay in %1 minute(s) %2 seconds.
#define DISC_LEVEL_ERROR 503 //You must be a level %1 ... to use this discipline.
#define DISCIPLINE_CANUSEIN 504 //You can use a new discipline in %1 minutes %2 seconds.
#define PVP_ON 552 //You are now player kill and follow the ways of Discord.

View File

@ -1269,6 +1269,22 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
break;
}
case ServerOP_IsOwnerOnline: {
auto o = (ServerIsOwnerOnline_Struct*)pack->pBuffer;
if (zone) {
if (o->zone_id != zone->GetZoneID()) {
break;
}
Corpse* c = entity_list.GetCorpseByID(o->corpse_id);
if (c && o->online) {
c->SetOwnerOnline(true);
} else if (c) {
c->SetOwnerOnline(false);
}
}
break;
}
case ServerOP_OOZGroupMessage: {
ServerGroupChannelMessage_Struct* gcm = (ServerGroupChannelMessage_Struct*)pack->pBuffer;
if (zone) {
@ -1587,12 +1603,14 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break;
}
case ServerOP_SpawnPlayerCorpse: {
SpawnPlayerCorpse_Struct* s = (SpawnPlayerCorpse_Struct*)pack->pBuffer;
Corpse* NewCorpse = database.LoadCharacterCorpse(s->player_corpse_id);
if (NewCorpse)
NewCorpse->Spawn();
else
auto *s = (SpawnPlayerCorpse_Struct *) pack->pBuffer;
Corpse *c = database.LoadCharacterCorpse(s->player_corpse_id);
if (c) {
c->Spawn();
}
else {
LogError("Unable to load player corpse id [{}] for zone [{}]", s->player_corpse_id, zone->GetShortName());
}
break;
}

View File

@ -3774,7 +3774,7 @@ uint32 ZoneDatabase::SaveCharacterCorpse(
const std::string& name,
uint32 zone_id,
uint16 instance_id,
const CharacterCorpseEntry& corpse,
const CharacterCorpseEntry& c,
const glm::vec4& position,
uint32 guild_consent_id
)
@ -3791,66 +3791,69 @@ uint32 ZoneDatabase::SaveCharacterCorpse(
e.heading = position.w;
e.guild_consent_id = guild_consent_id;
e.time_of_death = std::time(nullptr);
e.is_locked = corpse.locked;
e.exp = corpse.exp;
e.size = corpse.size;
e.level = corpse.level;
e.race = corpse.race;
e.gender = corpse.gender;
e.class_ = corpse.class_;
e.deity = corpse.deity;
e.texture = corpse.texture;
e.helm_texture = corpse.helmtexture;
e.copper = corpse.copper;
e.silver = corpse.silver;
e.gold = corpse.gold;
e.platinum = corpse.plat;
e.hair_color = corpse.haircolor;
e.beard_color = corpse.beardcolor;
e.eye_color_1 = corpse.eyecolor1;
e.eye_color_2 = corpse.eyecolor2;
e.hair_style = corpse.hairstyle;
e.face = corpse.face;
e.beard = corpse.beard;
e.drakkin_heritage = corpse.drakkin_heritage;
e.drakkin_tattoo = corpse.drakkin_tattoo;
e.drakkin_details = corpse.drakkin_details;
e.wc_1 = corpse.item_tint.Head.Color;
e.wc_2 = corpse.item_tint.Chest.Color;
e.wc_3 = corpse.item_tint.Arms.Color;
e.wc_4 = corpse.item_tint.Wrist.Color;
e.wc_5 = corpse.item_tint.Hands.Color;
e.wc_6 = corpse.item_tint.Legs.Color;
e.wc_7 = corpse.item_tint.Feet.Color;
e.wc_8 = corpse.item_tint.Primary.Color;
e.wc_9 = corpse.item_tint.Secondary.Color;
e.is_locked = c.locked;
e.exp = c.exp;
e.size = c.size;
e.level = c.level;
e.race = c.race;
e.gender = c.gender;
e.class_ = c.class_;
e.deity = c.deity;
e.texture = c.texture;
e.helm_texture = c.helmtexture;
e.copper = c.copper;
e.silver = c.silver;
e.gold = c.gold;
e.platinum = c.plat;
e.hair_color = c.haircolor;
e.beard_color = c.beardcolor;
e.eye_color_1 = c.eyecolor1;
e.eye_color_2 = c.eyecolor2;
e.hair_style = c.hairstyle;
e.face = c.face;
e.beard = c.beard;
e.drakkin_heritage = c.drakkin_heritage;
e.drakkin_tattoo = c.drakkin_tattoo;
e.drakkin_details = c.drakkin_details;
e.wc_1 = c.item_tint.Head.Color;
e.wc_2 = c.item_tint.Chest.Color;
e.wc_3 = c.item_tint.Arms.Color;
e.wc_4 = c.item_tint.Wrist.Color;
e.wc_5 = c.item_tint.Hands.Color;
e.wc_6 = c.item_tint.Legs.Color;
e.wc_7 = c.item_tint.Feet.Color;
e.wc_8 = c.item_tint.Primary.Color;
e.wc_9 = c.item_tint.Secondary.Color;
e.killed_by = c.killed_by;
e.rezzable = c.rezzable;
e.rez_time = c.rez_time;
e = CharacterCorpsesRepository::InsertOne(*this, e);
std::vector<CharacterCorpseItemsRepository::CharacterCorpseItems> v;
v.reserve(corpse.items.size());
v.reserve(c.items.size());
auto i = CharacterCorpseItemsRepository::NewEntity();
auto ci = CharacterCorpseItemsRepository::NewEntity();
for (const auto& item : corpse.items) {
i.corpse_id = e.id;
i.equip_slot = item.equip_slot;
i.item_id = item.item_id;
i.charges = item.charges;
i.aug_1 = item.aug_1;
i.aug_2 = item.aug_2;
i.aug_3 = item.aug_3;
i.aug_4 = item.aug_4;
i.aug_5 = item.aug_5;
i.aug_6 = item.aug_6;
i.attuned = item.attuned;
i.custom_data = item.custom_data;
i.ornamenticon = item.ornamenticon;
i.ornamentidfile = item.ornamentidfile;
i.ornament_hero_model = item.ornament_hero_model;
for (const auto& i : c.items) {
ci.corpse_id = e.id;
ci.equip_slot = i.equip_slot;
ci.item_id = i.item_id;
ci.charges = i.charges;
ci.aug_1 = i.aug_1;
ci.aug_2 = i.aug_2;
ci.aug_3 = i.aug_3;
ci.aug_4 = i.aug_4;
ci.aug_5 = i.aug_5;
ci.aug_6 = i.aug_6;
ci.attuned = i.attuned;
ci.custom_data = i.custom_data;
ci.ornamenticon = i.ornamenticon;
ci.ornamentidfile = i.ornamentidfile;
ci.ornament_hero_model = i.ornament_hero_model;
v.emplace_back(i);
v.emplace_back(ci);
}
if (!v.empty()) {
@ -3898,6 +3901,8 @@ uint32 ZoneDatabase::GetCharacterCorpseID(uint32 character_id, uint8 corpse_limi
uint32 ZoneDatabase::GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slot_id)
{
LogCorpsesDetail("corpse_id [{}] slot_id [{}]", corpse_id, slot_id);
Corpse* c = LoadCharacterCorpse(corpse_id);
uint32 item_id = 0;
@ -3909,76 +3914,6 @@ uint32 ZoneDatabase::GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slot_id)
return item_id;
}
bool ZoneDatabase::LoadCharacterCorpseData(uint32 corpse_id, CharacterCorpseEntry& corpse)
{
const auto& e = CharacterCorpsesRepository::FindOne(*this, corpse_id);
corpse.locked = e.is_locked;
corpse.exp = e.exp;
corpse.size = e.size;
corpse.level = e.level;
corpse.race = e.race;
corpse.gender = e.gender;
corpse.class_ = e.class_;
corpse.deity = e.deity;
corpse.texture = e.texture;
corpse.helmtexture = e.helm_texture;
corpse.copper = e.copper;
corpse.silver = e.silver;
corpse.gold = e.gold;
corpse.plat = e.platinum;
corpse.haircolor = e.hair_color;
corpse.beardcolor = e.beard_color;
corpse.eyecolor1 = e.eye_color_1;
corpse.eyecolor2 = e.eye_color_2;
corpse.hairstyle = e.hair_style;
corpse.face = e.face;
corpse.beard = e.beard;
corpse.drakkin_heritage = e.drakkin_heritage;
corpse.drakkin_tattoo = e.drakkin_tattoo;
corpse.drakkin_details = e.drakkin_details;
corpse.item_tint.Head.Color = e.wc_1;
corpse.item_tint.Chest.Color = e.wc_2;
corpse.item_tint.Arms.Color = e.wc_3;
corpse.item_tint.Wrist.Color = e.wc_4;
corpse.item_tint.Hands.Color = e.wc_5;
corpse.item_tint.Legs.Color = e.wc_6;
corpse.item_tint.Feet.Color = e.wc_7;
corpse.item_tint.Primary.Color = e.wc_8;
corpse.item_tint.Secondary.Color = e.wc_9;
const auto& l = CharacterCorpseItemsRepository::GetWhere(
*this,
fmt::format(
"`corpse_id` = {}",
corpse_id
)
);
for (const auto& e : l) {
CharacterCorpseItemEntry item{
.item_id = e.item_id,
.equip_slot = static_cast<int16>(e.equip_slot),
.charges = static_cast<uint16>(e.charges),
.aug_1 = e.aug_1,
.aug_2 = e.aug_2,
.aug_3 = e.aug_3,
.aug_4 = e.aug_4,
.aug_5 = e.aug_5,
.aug_6 = static_cast<uint32>(e.aug_6),
.attuned = e.attuned == 1,
.custom_data = e.custom_data,
.ornamenticon = e.ornamenticon,
.ornamentidfile = e.ornamentidfile,
.ornament_hero_model = e.ornament_hero_model
};
corpse.items.emplace_back(std::move(item));
}
return true;
}
Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(
uint32 character_id,
uint32 zone_id,
@ -3997,23 +3932,13 @@ Corpse* ZoneDatabase::SummonBuriedCharacterCorpses(
);
for (const auto& e : l) {
c = Corpse::LoadCharacterCorpseEntity(
e.id,
e.charid,
e.charname,
position,
std::to_string(e.time_of_death),
e.is_rezzed == 1,
RuleB(Zone, EnableShadowrest) ? 0 : e.was_at_graveyard,
e.guild_consent_id
);
c = Corpse::LoadCharacterCorpse(e, position);
if (!c) {
continue;
}
entity_list.AddCorpse(c);
c->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS));
c->SetDecayTimer(RuleI(Character, CorpseDecayTime));
c->Spawn();
if (!UnburyCharacterCorpse(c->GetCorpseDBID(), zone_id, instance_id, position)) {
@ -4052,20 +3977,10 @@ bool ZoneDatabase::SummonAllCharacterCorpses(
e.is_buried = 0;
e.was_at_graveyard = 0;
c = Corpse::LoadCharacterCorpseEntity(
e.id,
e.charid,
e.charname,
position,
std::to_string(e.time_of_death),
e.is_rezzed == 1,
RuleB(Zone, EnableShadowrest) ? 0 : e.was_at_graveyard,
e.guild_consent_id
);
c = Corpse::LoadCharacterCorpse(e, position);
if (c) {
entity_list.AddCorpse(c);
c->SetDecayTimer(RuleI(Character, CorpseDecayTimeMS));
c->SetDecayTimer(RuleI(Character, CorpseDecayTime));
c->Spawn();
++corpse_count;
} else {
@ -4108,26 +4023,16 @@ bool ZoneDatabase::UnburyCharacterCorpse(uint32 corpse_id, uint32 zone_id, uint1
Corpse* ZoneDatabase::LoadCharacterCorpse(uint32 corpse_id)
{
const auto& e = CharacterCorpsesRepository::FindOne(*this, corpse_id);
Corpse* c = 0;
if (!e.id) {
return c;
if (!corpse_id) {
return nullptr;
}
glm::vec4 position = glm::vec4(e.x, e.y, e.z, e.heading);
const auto &e = CharacterCorpsesRepository::FindOne(*this, corpse_id);
if (!e.id) {
return nullptr;
}
c = Corpse::LoadCharacterCorpseEntity(
e.id,
e.charid,
e.charname,
position,
std::to_string(e.time_of_death),
e.is_rezzed == 1,
RuleB(Zone, EnableShadowrest) ? 0 : e.was_at_graveyard,
e.guild_consent_id
);
auto c = Corpse::LoadCharacterCorpse(e, glm::vec4(e.x, e.y, e.z, e.heading));
entity_list.AddCorpse(c);
@ -4146,21 +4051,9 @@ bool ZoneDatabase::LoadCharacterCorpses(uint32 zone_id, uint16 instance_id)
)
);
for (const auto& e : l) {
for (const auto &e: l) {
glm::vec4 position = glm::vec4(e.x, e.y, e.z, e.heading);
entity_list.AddCorpse(
Corpse::LoadCharacterCorpseEntity(
e.id,
e.charid,
e.charname,
position,
std::to_string(e.time_of_death),
e.is_rezzed == 1,
RuleB(Zone, EnableShadowrest) ? 0 : e.was_at_graveyard,
e.guild_consent_id
)
);
entity_list.AddCorpse(Corpse::LoadCharacterCorpse(e, position));
}
return true;

View File

@ -307,7 +307,8 @@ struct CharacterCorpseItemEntry
struct CharacterCorpseEntry
{
bool locked;
uint32 exp;
uint64 exp;
uint64 gm_exp;
float size;
uint8 level;
uint32 race;
@ -328,6 +329,9 @@ struct CharacterCorpseEntry
uint8 hairstyle;
uint8 face;
uint8 beard;
uint8 killed_by;
bool rezzable;
uint32 rez_time;
uint32 drakkin_heritage;
uint32 drakkin_tattoo;
uint32 drakkin_details;
@ -493,7 +497,6 @@ public:
bool GetDecayTimes(npcDecayTimes_Struct* npc_decay_times);
uint32 GetFirstCorpseID(uint32 character_id);
Corpse* LoadCharacterCorpse(uint32 corpse_id);
bool LoadCharacterCorpseData(uint32 corpse_id, CharacterCorpseEntry &corpse);
bool LoadCharacterCorpses(uint32 zone_id, uint16 instance_id);
void MarkCorpseAsResurrected(uint32 corpse_id);
uint32 SaveCharacterCorpse(uint32 character_id, const std::string& name, uint32 zone_id, uint16 instance_id, const CharacterCorpseEntry& c, const glm::vec4& position, uint32 guild_consent_id);