[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
35 changed files with 1783 additions and 1289 deletions
+1
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. */
@@ -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{
+20
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);
}
+2
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",
};
}
+10
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__);\
@@ -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) + ")");
}
@@ -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
)
);
+8 -5
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)")
+9
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 {
+1 -1
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
)
)
);
+1
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
+1 -1
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