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: 59db2eeea0
This commit is contained in:
Andrew 2025-02-12 15:06:42 -03:00 committed by GitHub
parent 5421f13c13
commit 16fa25b7a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 118 additions and 2 deletions

View File

@ -67,6 +67,7 @@
# PLAYER DUMP
# CUSTOM
# 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 #

View File

@ -1974,6 +1974,7 @@ void Creature::setDeathState(DeathState state, bool despawn)
if (state == DeathState::JustDied)
{
m_corpseRemoveTime = GameTime::GetGameTime().count() + m_corpseDelay;
GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelay);
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay + m_corpseDelay;
// always save boss respawn time at death to prevent crash cheating

View File

@ -897,6 +897,7 @@ void GameObject::Update(uint32 diff)
return;
}
GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelayTime);
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelayTime;
// if option not set then object will be saved at grid unload

View File

@ -1245,9 +1245,7 @@ void Player::UpdateArea(uint32 newArea)
void Player::UpdateZone(uint32 newZone, uint32 newArea)
{
if (!newZone)
{
return;
}
if (m_zoneUpdateId != newZone)
{
@ -1264,6 +1262,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone);
}
GetMap()->UpdatePlayerZoneStats(m_zoneUpdateId, newZone);
// group update
if (GetGroup())
SetGroupUpdateFlag(GROUP_UPDATE_FULL);

View File

@ -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_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
#define MAP_INVALID_ZONE 0xFFFFFFFF
ZoneDynamicInfo::ZoneDynamicInfo() : MusicId(0), WeatherId(WEATHER_STATE_FINE),
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
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*/)
{
if (t_diff)
@ -913,6 +934,8 @@ struct ResetNotifier
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
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)
{
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)

View File

@ -594,6 +594,9 @@ public:
void DeleteRespawnTimes();
[[nodiscard]] time_t GetInstanceResetPeriod() const { return _instanceResetPeriod; }
void UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone);
void ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32& respawnDelay) const;
TaskScheduler _creatureRespawnScheduler;
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> _goRespawnTimes;
std::unordered_map<uint32, uint32> _zonePlayerCountMap;
ZoneDynamicInfoMap _zoneDynamicInfo;
uint32 _defaultLight;

View File

@ -201,6 +201,8 @@ enum WorldFloatConfigs
CONFIG_ARENA_WIN_RATING_MODIFIER_2,
CONFIG_ARENA_LOSE_RATING_MODIFIER,
CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER,
CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT,
CONFIG_RESPAWN_DYNAMICRATE_CREATURE,
FLOAT_CONFIG_VALUE_COUNT
};
@ -420,6 +422,8 @@ enum WorldIntConfigs
CONFIG_AUCTIONHOUSE_WORKERTHREADS,
CONFIG_SPELL_QUEUE_WINDOW,
CONFIG_SUNSREACH_COUNTER_MAX,
CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT,
CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE,
INT_CONFIG_VALUE_COUNT
};

View File

@ -1295,6 +1295,13 @@ void World::LoadConfigSettings(bool reload)
_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
std::string dataPath = sConfigMgr->GetOption<std::string>("DataDir", "./");
if (dataPath.empty() || (dataPath.at(dataPath.length() - 1) != '/' && dataPath.at(dataPath.length() - 1) != '\\'))