mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
# 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>
593 lines
14 KiB
C++
593 lines
14 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2015 EQEMu Development Team (http://eqemulator.net)
|
|
|
|
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
|
|
*/
|
|
|
|
#include "../common/global_define.h"
|
|
#include "../common/rulesys.h"
|
|
#include "../common/strings.h"
|
|
#include "../common/timer.h"
|
|
#include "../common/repositories/character_corpses_repository.h"
|
|
#include "../common/repositories/dynamic_zone_members_repository.h"
|
|
#include "../common/repositories/dynamic_zones_repository.h"
|
|
#include "../common/repositories/group_id_repository.h"
|
|
#include "../common/repositories/instance_list_repository.h"
|
|
#include "../common/repositories/instance_list_player_repository.h"
|
|
#include "../common/repositories/raid_members_repository.h"
|
|
#include "../common/repositories/respawn_times_repository.h"
|
|
#include "../common/repositories/spawn_condition_values_repository.h"
|
|
#include "repositories/spawn2_disabled_repository.h"
|
|
|
|
|
|
#include "database.h"
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
|
|
// Disgrace: for windows compile
|
|
#ifdef _WINDOWS
|
|
#include <windows.h>
|
|
#define snprintf _snprintf
|
|
#define strncasecmp _strnicmp
|
|
#define strcasecmp _stricmp
|
|
#else
|
|
#include "unix.h"
|
|
#include "../zone/zonedb.h"
|
|
#include <netinet/in.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
|
|
bool Database::AddClientToInstance(uint16 instance_id, uint32 character_id)
|
|
{
|
|
auto e = InstanceListPlayerRepository::NewEntity();
|
|
|
|
e.id = instance_id;
|
|
e.charid = character_id;
|
|
|
|
return InstanceListPlayerRepository::ReplaceOne(*this, e);
|
|
}
|
|
|
|
bool Database::CheckInstanceByCharID(uint16 instance_id, uint32 character_id)
|
|
{
|
|
if (!instance_id) {
|
|
return false;
|
|
}
|
|
|
|
auto l = InstanceListPlayerRepository::GetWhere(
|
|
*this,
|
|
fmt::format(
|
|
"id = {} AND charid = {}",
|
|
instance_id,
|
|
character_id
|
|
)
|
|
);
|
|
if (l.empty()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Database::CheckInstanceExists(uint16 instance_id)
|
|
{
|
|
if (!instance_id) {
|
|
return false;
|
|
}
|
|
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Database::CheckInstanceExpired(uint16 instance_id)
|
|
{
|
|
if (!instance_id) {
|
|
return true;
|
|
}
|
|
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return true;
|
|
}
|
|
|
|
if (i.never_expires) {
|
|
return false;
|
|
}
|
|
|
|
timeval tv{};
|
|
gettimeofday(&tv, nullptr);
|
|
|
|
return (i.start_time + i.duration) <= tv.tv_sec;
|
|
}
|
|
|
|
bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration)
|
|
{
|
|
auto e = InstanceListRepository::NewEntity();
|
|
|
|
e.id = instance_id;
|
|
e.zone = zone_id;
|
|
e.version = version;
|
|
e.start_time = std::time(nullptr);
|
|
e.duration = duration;
|
|
|
|
return InstanceListRepository::InsertOne(*this, e).id;
|
|
}
|
|
|
|
bool Database::GetUnusedInstanceID(uint16 &instance_id)
|
|
{
|
|
uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances);
|
|
uint32 max_instance_id = 32000;
|
|
|
|
// sanity check reserved
|
|
if (max_reserved_instance_id >= max_instance_id) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
// recycle instances
|
|
if (RuleB(Instances, RecycleInstanceIds)) {
|
|
|
|
//query to get first unused id above reserved
|
|
auto query = fmt::format(
|
|
SQL(
|
|
SELECT id
|
|
FROM instance_list
|
|
WHERE id = {};
|
|
),
|
|
max_reserved_instance_id + 1
|
|
);
|
|
|
|
auto results = QueryDatabase(query);
|
|
|
|
// could not successfully query - bail out
|
|
if (!results.Success()) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
// first id is available
|
|
if (results.RowCount() == 0) {
|
|
instance_id = max_reserved_instance_id + 1;
|
|
return true;
|
|
}
|
|
|
|
// now look for next available above reserved
|
|
query = fmt::format(
|
|
SQL(
|
|
SELECT MIN(i.id + 1) AS next_available
|
|
FROM instance_list i
|
|
LEFT JOIN instance_list i2 ON i.id + 1 = i2.id
|
|
WHERE i.id >= {}
|
|
AND i2.id IS NULL;
|
|
),
|
|
max_reserved_instance_id
|
|
);
|
|
|
|
results = QueryDatabase(query);
|
|
|
|
// could not successfully query - bail out
|
|
if (!results.Success()) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
// did not retrieve any rows - bail out
|
|
if (results.RowCount() == 0) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
auto row = results.begin();
|
|
|
|
// check that id is within limits
|
|
if (row[0] && Strings::ToInt(row[0]) <= max_instance_id) {
|
|
instance_id = Strings::ToInt(row[0]);
|
|
return true;
|
|
}
|
|
|
|
// no available instance ids
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
// get max unused id above reserved
|
|
auto query = fmt::format(
|
|
"SELECT IFNULL(MAX(id), {}) + 1 FROM instance_list WHERE id > {}",
|
|
max_reserved_instance_id,
|
|
max_reserved_instance_id
|
|
);
|
|
|
|
auto results = QueryDatabase(query);
|
|
|
|
// could not successfully query - bail out
|
|
if (!results.Success()) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
// did not retrieve any rows - bail out
|
|
if (results.RowCount() == 0) {
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
auto row = results.begin();
|
|
|
|
// no instances currently used
|
|
if (!row[0]) {
|
|
instance_id = max_reserved_instance_id + 1;
|
|
return true;
|
|
}
|
|
|
|
// check that id is within limits
|
|
if (Strings::ToInt(row[0]) <= max_instance_id) {
|
|
instance_id = Strings::ToInt(row[0]);
|
|
return true;
|
|
}
|
|
|
|
// no available instance ids
|
|
instance_id = 0;
|
|
return false;
|
|
}
|
|
|
|
bool Database::IsGlobalInstance(uint16 instance_id)
|
|
{
|
|
if (!instance_id) {
|
|
return false;
|
|
}
|
|
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return false;
|
|
}
|
|
|
|
return i.is_global;
|
|
}
|
|
|
|
bool Database::RemoveClientFromInstance(uint16 instance_id, uint32 char_id)
|
|
{
|
|
return InstanceListPlayerRepository::DeleteWhere(
|
|
*this,
|
|
fmt::format(
|
|
"id = {} AND charid = {}",
|
|
instance_id,
|
|
char_id
|
|
)
|
|
);
|
|
}
|
|
|
|
bool Database::RemoveClientsFromInstance(uint16 instance_id)
|
|
{
|
|
return InstanceListPlayerRepository::DeleteOne(*this, instance_id);
|
|
}
|
|
|
|
bool Database::VerifyInstanceAlive(uint16 instance_id, uint32 character_id)
|
|
{
|
|
//we are not saved to this instance so set our instance to 0
|
|
if (!IsGlobalInstance(instance_id) && !CheckInstanceByCharID(instance_id, character_id)) {
|
|
return false;
|
|
}
|
|
|
|
if (CheckInstanceExpired(instance_id)) {
|
|
DeleteInstance(instance_id);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Database::VerifyZoneInstance(uint32 zone_id, uint16 instance_id)
|
|
{
|
|
auto l = InstanceListRepository::GetWhere(
|
|
*this,
|
|
fmt::format(
|
|
"id = {} AND zone = {}",
|
|
instance_id,
|
|
zone_id
|
|
)
|
|
);
|
|
if (l.empty()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16 Database::GetInstanceID(uint32 zone_id, uint32 character_id, int16 version)
|
|
{
|
|
if (!zone_id) {
|
|
return 0;
|
|
}
|
|
|
|
const auto query = fmt::format(
|
|
"SELECT instance_list.id FROM "
|
|
"instance_list, instance_list_player WHERE "
|
|
"instance_list.zone = {} AND "
|
|
"instance_list.version = {} AND "
|
|
"instance_list.id = instance_list_player.id AND "
|
|
"instance_list_player.charid = {} "
|
|
"LIMIT 1;",
|
|
zone_id,
|
|
version,
|
|
character_id
|
|
);
|
|
auto results = QueryDatabase(query);
|
|
|
|
if (!results.Success() || !results.RowCount()) {
|
|
return 0;
|
|
}
|
|
|
|
auto row = results.begin();
|
|
|
|
return static_cast<uint16>(Strings::ToUnsignedInt(row[0]));
|
|
}
|
|
|
|
std::vector<uint16> Database::GetInstanceIDs(uint32 zone_id, uint32 character_id)
|
|
{
|
|
std::vector<uint16> l;
|
|
|
|
if (!zone_id) {
|
|
return l;
|
|
}
|
|
|
|
const auto query = fmt::format(
|
|
"SELECT instance_list.id FROM "
|
|
"instance_list, instance_list_player WHERE "
|
|
"instance_list.zone = {} AND "
|
|
"instance_list.id = instance_list_player.id AND "
|
|
"instance_list_player.charid = {}",
|
|
zone_id,
|
|
character_id
|
|
);
|
|
auto results = QueryDatabase(query);
|
|
|
|
if (!results.Success() || !results.RowCount()) {
|
|
return l;
|
|
}
|
|
|
|
for (auto row : results) {
|
|
l.push_back(static_cast<uint16>(Strings::ToUnsignedInt(row[0])));
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
uint8_t Database::GetInstanceVersion(uint16 instance_id) {
|
|
if (!instance_id) {
|
|
return 0;
|
|
}
|
|
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return 0;
|
|
}
|
|
|
|
return i.version;
|
|
}
|
|
|
|
uint32 Database::GetTimeRemainingInstance(uint16 instance_id, bool &is_perma)
|
|
{
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
is_perma = false;
|
|
return 0;
|
|
}
|
|
|
|
if (i.never_expires) {
|
|
is_perma = true;
|
|
return 0;
|
|
}
|
|
|
|
is_perma = false;
|
|
|
|
timeval tv;
|
|
gettimeofday(&tv, nullptr);
|
|
return ((i.start_time + i.duration) - tv.tv_sec);
|
|
}
|
|
|
|
uint32 Database::GetInstanceZoneID(uint16 instance_id)
|
|
{
|
|
if (!instance_id) {
|
|
return 0;
|
|
}
|
|
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return 0;
|
|
}
|
|
|
|
return i.zone;
|
|
}
|
|
|
|
void Database::AssignGroupToInstance(uint32 group_id, uint32 instance_id)
|
|
{
|
|
auto zone_id = GetInstanceZoneID(instance_id);
|
|
auto version = GetInstanceVersion(instance_id);
|
|
|
|
auto l = GroupIdRepository::GetWhere(
|
|
*this,
|
|
fmt::format(
|
|
"groupid = {}",
|
|
group_id
|
|
)
|
|
);
|
|
if (l.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& e : l) {
|
|
if (!GetInstanceID(zone_id, e.charid, version)) {
|
|
AddClientToInstance(instance_id, e.charid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Database::AssignRaidToInstance(uint32 raid_id, uint32 instance_id)
|
|
{
|
|
auto zone_id = GetInstanceZoneID(instance_id);
|
|
auto version = GetInstanceVersion(instance_id);
|
|
|
|
auto l = RaidMembersRepository::GetWhere(
|
|
*this,
|
|
fmt::format(
|
|
"raidid = {}",
|
|
raid_id
|
|
)
|
|
);
|
|
if (l.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& e : l) {
|
|
if (!GetInstanceID(zone_id, e.charid, version)) {
|
|
AddClientToInstance(instance_id, e.charid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Database::DeleteInstance(uint16 instance_id)
|
|
{
|
|
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id = {}", instance_id));
|
|
|
|
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
|
|
|
|
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
|
|
|
|
DynamicZoneMembersRepository::DeleteByInstance(*this, instance_id);
|
|
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
|
|
|
|
CharacterCorpsesRepository::BuryInstance(*this, instance_id);
|
|
}
|
|
|
|
void Database::FlagInstanceByGroupLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 group_id)
|
|
{
|
|
auto instance_id = GetInstanceID(zone_id, character_id, version);
|
|
if (instance_id) {
|
|
return;
|
|
}
|
|
|
|
char ln[128];
|
|
memset(ln, 0, 128);
|
|
GetGroupLeadershipInfo(group_id, ln);
|
|
|
|
auto group_leader_id = GetCharacterID((const char*)ln);
|
|
auto group_leader_instance_id = GetInstanceID(zone_id, group_leader_id, version);
|
|
|
|
if (!group_leader_instance_id) {
|
|
return;
|
|
}
|
|
|
|
AddClientToInstance(group_leader_instance_id, character_id);
|
|
}
|
|
|
|
void Database::FlagInstanceByRaidLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 raid_id)
|
|
{
|
|
uint16 instance_id = GetInstanceID(zone_id, character_id, version);
|
|
if (instance_id) {
|
|
return;
|
|
}
|
|
|
|
auto raid_leader_id = GetCharacterID(GetRaidLeaderName(raid_id));
|
|
auto raid_leader_instance_id = GetInstanceID(zone_id, raid_leader_id, version);
|
|
|
|
if (!raid_leader_instance_id) {
|
|
return;
|
|
}
|
|
|
|
AddClientToInstance(raid_leader_instance_id, character_id);
|
|
}
|
|
|
|
void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &character_ids)
|
|
{
|
|
auto l = InstanceListPlayerRepository::GetWhere(*this, fmt::format("id = {}", instance_id));
|
|
if (l.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& e : l) {
|
|
character_ids.push_back(e.charid);
|
|
}
|
|
}
|
|
|
|
void Database::PurgeExpiredInstances()
|
|
{
|
|
/**
|
|
* Delay purging by a day so that we can continue using adjacent free instance id's
|
|
* from the table without risking the chance we immediately re-allocate a zone that freshly expired but
|
|
* has not been fully de-allocated
|
|
*/
|
|
auto l = InstanceListRepository::GetWhere(
|
|
*this,
|
|
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND 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);
|
|
|
|
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
|
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
|
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
|
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
|
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
|
|
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
|
|
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
|
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
|
}
|
|
|
|
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
|
{
|
|
auto i = InstanceListRepository::FindOne(*this, instance_id);
|
|
if (!i.id) {
|
|
return;
|
|
}
|
|
|
|
i.start_time = std::time(nullptr);
|
|
i.duration = 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);
|
|
}
|