feat(Core/Maps): Implement dynamic respawn rates (#21417)
Co-authored-by: r00ty-tc <r00ty-tc@users.noreply.github.com> Co-authored-by: Jelle Meeus <sogladev@gmail.com> Co-authored-by Treeston This is a partial cherry-pick of: https://github.com/TrinityCore/TrinityCore/pull/19056/commits/59db2eeea0a35028779fd76372ae06cc98c8086f
This commit is contained in:
@@ -67,6 +67,7 @@
|
|||||||
# PLAYER DUMP
|
# PLAYER DUMP
|
||||||
# CUSTOM
|
# CUSTOM
|
||||||
# DEBUG
|
# DEBUG
|
||||||
|
# DYNAMIC RESPAWN SETTINGS
|
||||||
#
|
#
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
|
|
||||||
@@ -4467,6 +4468,46 @@ Debug.Arena = 0
|
|||||||
#
|
#
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
|
|
||||||
|
###################################################################################################
|
||||||
|
# DYNAMIC RESPAWN SETTINGS
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Respawn.DynamicRateCreature
|
||||||
|
# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for creatures).
|
||||||
|
# Up to this number of players, the respawn rate is unchanged.
|
||||||
|
# Does not affect instanced creatures, bosses or quest givers.
|
||||||
|
# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth.
|
||||||
|
# Default: 1 (Disabled)
|
||||||
|
|
||||||
|
Respawn.DynamicRateCreature = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Respawn.DynamicMinimumCreature
|
||||||
|
# Description: The minimum respawn time for a creature under dynamic scaling.
|
||||||
|
# Default: 10 - (10 seconds)
|
||||||
|
|
||||||
|
Respawn.DynamicMinimumCreature = 10
|
||||||
|
|
||||||
|
#
|
||||||
|
# Respawn.DynamicRateGameObject
|
||||||
|
# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for gameobjects).
|
||||||
|
# Up to this number of players, the respawn rate is unchanged.
|
||||||
|
# Does not affect instanced objects or quest givers.
|
||||||
|
# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth.
|
||||||
|
# Default: 1 (Disabled)
|
||||||
|
|
||||||
|
Respawn.DynamicRateGameObject = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Respawn.DynamicMinimumGameObject
|
||||||
|
# Description: The minimum respawn time for a gameobject under dynamic scaling.
|
||||||
|
# Default: 10 - (10 seconds)
|
||||||
|
|
||||||
|
Respawn.DynamicMinimumGameObject = 10
|
||||||
|
|
||||||
|
#
|
||||||
|
###################################################################################################
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
# #
|
# #
|
||||||
# GAME SETTINGS END #
|
# GAME SETTINGS END #
|
||||||
|
|||||||
@@ -1974,6 +1974,7 @@ void Creature::setDeathState(DeathState state, bool despawn)
|
|||||||
if (state == DeathState::JustDied)
|
if (state == DeathState::JustDied)
|
||||||
{
|
{
|
||||||
m_corpseRemoveTime = GameTime::GetGameTime().count() + m_corpseDelay;
|
m_corpseRemoveTime = GameTime::GetGameTime().count() + m_corpseDelay;
|
||||||
|
GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelay);
|
||||||
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay + m_corpseDelay;
|
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay + m_corpseDelay;
|
||||||
|
|
||||||
// always save boss respawn time at death to prevent crash cheating
|
// always save boss respawn time at death to prevent crash cheating
|
||||||
|
|||||||
@@ -897,6 +897,7 @@ void GameObject::Update(uint32 diff)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelayTime);
|
||||||
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelayTime;
|
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelayTime;
|
||||||
|
|
||||||
// if option not set then object will be saved at grid unload
|
// if option not set then object will be saved at grid unload
|
||||||
|
|||||||
@@ -1245,9 +1245,7 @@ void Player::UpdateArea(uint32 newArea)
|
|||||||
void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
||||||
{
|
{
|
||||||
if (!newZone)
|
if (!newZone)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (m_zoneUpdateId != newZone)
|
if (m_zoneUpdateId != newZone)
|
||||||
{
|
{
|
||||||
@@ -1264,6 +1262,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
|||||||
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
|
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetMap()->UpdatePlayerZoneStats(m_zoneUpdateId, newZone);
|
||||||
|
|
||||||
// group update
|
// group update
|
||||||
if (GetGroup())
|
if (GetGroup())
|
||||||
SetGroupUpdateFlag(GROUP_UPDATE_FULL);
|
SetGroupUpdateFlag(GROUP_UPDATE_FULL);
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ u_map_magic MapLiquidMagic = { {'M', 'L', 'I', 'Q'} };
|
|||||||
static uint16 const holetab_h[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
|
static uint16 const holetab_h[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
|
||||||
static uint16 const holetab_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
|
static uint16 const holetab_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
|
||||||
|
|
||||||
|
#define MAP_INVALID_ZONE 0xFFFFFFFF
|
||||||
|
|
||||||
ZoneDynamicInfo::ZoneDynamicInfo() : MusicId(0), WeatherId(WEATHER_STATE_FINE),
|
ZoneDynamicInfo::ZoneDynamicInfo() : MusicId(0), WeatherId(WEATHER_STATE_FINE),
|
||||||
WeatherGrade(0.0f), OverrideLightId(0), LightFadeInTime(0) { }
|
WeatherGrade(0.0f), OverrideLightId(0), LightFadeInTime(0) { }
|
||||||
|
|
||||||
@@ -252,6 +254,8 @@ Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_zonePlayerCountMap.clear();
|
||||||
|
|
||||||
//lets initialize visibility distance for map
|
//lets initialize visibility distance for map
|
||||||
Map::InitVisibilityDistance();
|
Map::InitVisibilityDistance();
|
||||||
|
|
||||||
@@ -736,6 +740,23 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Acore::Objec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone)
|
||||||
|
{
|
||||||
|
// Nothing to do if no change
|
||||||
|
if (oldZone == newZone)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (oldZone != MAP_INVALID_ZONE)
|
||||||
|
{
|
||||||
|
uint32& oldZoneCount = _zonePlayerCountMap[oldZone];
|
||||||
|
if (!oldZoneCount)
|
||||||
|
LOG_ERROR("maps", "A player left zone {} (went to {}) - but there were no players in the zone!", oldZone, newZone);
|
||||||
|
else
|
||||||
|
--oldZoneCount;
|
||||||
|
}
|
||||||
|
++_zonePlayerCountMap[newZone];
|
||||||
|
}
|
||||||
|
|
||||||
void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
||||||
{
|
{
|
||||||
if (t_diff)
|
if (t_diff)
|
||||||
@@ -913,6 +934,8 @@ struct ResetNotifier
|
|||||||
|
|
||||||
void Map::RemovePlayerFromMap(Player* player, bool remove)
|
void Map::RemovePlayerFromMap(Player* player, bool remove)
|
||||||
{
|
{
|
||||||
|
// Before leaving map, update zone/area for stats
|
||||||
|
player->UpdateZone(MAP_INVALID_ZONE, 0);
|
||||||
player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
||||||
|
|
||||||
bool inWorld = player->IsInWorld();
|
bool inWorld = player->IsInWorld();
|
||||||
@@ -2635,6 +2658,40 @@ void Map::SendObjectUpdates()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32& respawnDelay) const
|
||||||
|
{
|
||||||
|
ASSERT(obj->GetMap() == this);
|
||||||
|
|
||||||
|
float rate = sWorld->getFloatConfig(obj->IsGameObject() ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE);
|
||||||
|
|
||||||
|
if (rate == 1.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// No instanced maps (dungeons, battlegrounds, arenas etc.)
|
||||||
|
if (obj->GetMap()->Instanceable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// No quest givers or world bosses
|
||||||
|
if (Creature const* creature = obj->ToCreature())
|
||||||
|
if (creature->IsQuestGiver() || creature->isWorldBoss())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto it = _zonePlayerCountMap.find(obj->GetZoneId());
|
||||||
|
if (it == _zonePlayerCountMap.end())
|
||||||
|
return;
|
||||||
|
uint32 const playerCount = it->second;
|
||||||
|
if (!playerCount)
|
||||||
|
return;
|
||||||
|
double const adjustFactor = rate / playerCount;
|
||||||
|
if (adjustFactor >= 1.0) // nothing to do here
|
||||||
|
return;
|
||||||
|
uint32 const timeMinimum = sWorld->getIntConfig(obj->IsGameObject() ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE);
|
||||||
|
if (respawnDelay <= timeMinimum)
|
||||||
|
return;
|
||||||
|
|
||||||
|
respawnDelay = std::max<uint32>(std::ceil(respawnDelay * adjustFactor), timeMinimum);
|
||||||
|
}
|
||||||
|
|
||||||
void Map::DelayedUpdate(const uint32 t_diff)
|
void Map::DelayedUpdate(const uint32 t_diff)
|
||||||
{
|
{
|
||||||
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
|
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
|
||||||
|
|||||||
@@ -594,6 +594,9 @@ public:
|
|||||||
void DeleteRespawnTimes();
|
void DeleteRespawnTimes();
|
||||||
[[nodiscard]] time_t GetInstanceResetPeriod() const { return _instanceResetPeriod; }
|
[[nodiscard]] time_t GetInstanceResetPeriod() const { return _instanceResetPeriod; }
|
||||||
|
|
||||||
|
void UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone);
|
||||||
|
void ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32& respawnDelay) const;
|
||||||
|
|
||||||
TaskScheduler _creatureRespawnScheduler;
|
TaskScheduler _creatureRespawnScheduler;
|
||||||
|
|
||||||
void ScheduleCreatureRespawn(ObjectGuid /*creatureGuid*/, Milliseconds /*respawnTimer*/, Position pos = Position());
|
void ScheduleCreatureRespawn(ObjectGuid /*creatureGuid*/, Milliseconds /*respawnTimer*/, Position pos = Position());
|
||||||
@@ -780,6 +783,8 @@ private:
|
|||||||
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes;
|
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes;
|
||||||
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes;
|
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes;
|
||||||
|
|
||||||
|
std::unordered_map<uint32, uint32> _zonePlayerCountMap;
|
||||||
|
|
||||||
ZoneDynamicInfoMap _zoneDynamicInfo;
|
ZoneDynamicInfoMap _zoneDynamicInfo;
|
||||||
uint32 _defaultLight;
|
uint32 _defaultLight;
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ enum WorldFloatConfigs
|
|||||||
CONFIG_ARENA_WIN_RATING_MODIFIER_2,
|
CONFIG_ARENA_WIN_RATING_MODIFIER_2,
|
||||||
CONFIG_ARENA_LOSE_RATING_MODIFIER,
|
CONFIG_ARENA_LOSE_RATING_MODIFIER,
|
||||||
CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER,
|
CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER,
|
||||||
|
CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT,
|
||||||
|
CONFIG_RESPAWN_DYNAMICRATE_CREATURE,
|
||||||
FLOAT_CONFIG_VALUE_COUNT
|
FLOAT_CONFIG_VALUE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -420,6 +422,8 @@ enum WorldIntConfigs
|
|||||||
CONFIG_AUCTIONHOUSE_WORKERTHREADS,
|
CONFIG_AUCTIONHOUSE_WORKERTHREADS,
|
||||||
CONFIG_SPELL_QUEUE_WINDOW,
|
CONFIG_SPELL_QUEUE_WINDOW,
|
||||||
CONFIG_SUNSREACH_COUNTER_MAX,
|
CONFIG_SUNSREACH_COUNTER_MAX,
|
||||||
|
CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT,
|
||||||
|
CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE,
|
||||||
INT_CONFIG_VALUE_COUNT
|
INT_CONFIG_VALUE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1295,6 +1295,13 @@ void World::LoadConfigSettings(bool reload)
|
|||||||
|
|
||||||
_int_configs[CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD] = sConfigMgr->GetOption<uint32>("DailyRBGArenaPoints.MinLevel", 71);
|
_int_configs[CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD] = sConfigMgr->GetOption<uint32>("DailyRBGArenaPoints.MinLevel", 71);
|
||||||
|
|
||||||
|
// Respawn
|
||||||
|
_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = sConfigMgr->GetOption<float>("Respawn.DynamicRateCreature", 1.0f);
|
||||||
|
_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE] = sConfigMgr->GetOption<int32>("Respawn.DynamicMinimumCreature", 10);
|
||||||
|
|
||||||
|
_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = sConfigMgr->GetOption<float>("Respawn.DynamicRateGameObject", 1.0f);
|
||||||
|
_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT] = sConfigMgr->GetOption<int32>("Respawn.DynamicMinimumGameObject", 10);
|
||||||
|
|
||||||
///- Read the "Data" directory from the config file
|
///- Read the "Data" directory from the config file
|
||||||
std::string dataPath = sConfigMgr->GetOption<std::string>("DataDir", "./");
|
std::string dataPath = sConfigMgr->GetOption<std::string>("DataDir", "./");
|
||||||
if (dataPath.empty() || (dataPath.at(dataPath.length() - 1) != '/' && dataPath.at(dataPath.length() - 1) != '\\'))
|
if (dataPath.empty() || (dataPath.at(dataPath.length() - 1) != '/' && dataPath.at(dataPath.length() - 1) != '\\'))
|
||||||
|
|||||||
Reference in New Issue
Block a user