diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 9fd477413..e0698fa55 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -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 # diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index f2e07c735..ad0cf07fe 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -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 diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 91778e42c..9ed685f9e 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -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 diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index b2a0b46a1..4ffd9efcf 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -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); diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index fb13330f5..33e4a8a2c 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -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, TypeContainerVisitorUpdateZone(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(std::ceil(respawnDelay * adjustFactor), timeMinimum); +} + void Map::DelayedUpdate(const uint32 t_diff) { for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index e6e10c26d..8763590e5 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -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 _creatureRespawnTimes; std::unordered_map _goRespawnTimes; + std::unordered_map _zonePlayerCountMap; + ZoneDynamicInfoMap _zoneDynamicInfo; uint32 _defaultLight; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 9cfcfd0b1..50e4dbfc3 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -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 }; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index d3df6afa0..76c2075b4 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1295,6 +1295,13 @@ void World::LoadConfigSettings(bool reload) _int_configs[CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD] = sConfigMgr->GetOption("DailyRBGArenaPoints.MinLevel", 71); + // Respawn + _float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = sConfigMgr->GetOption("Respawn.DynamicRateCreature", 1.0f); + _int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE] = sConfigMgr->GetOption("Respawn.DynamicMinimumCreature", 10); + + _float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = sConfigMgr->GetOption("Respawn.DynamicRateGameObject", 1.0f); + _int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT] = sConfigMgr->GetOption("Respawn.DynamicMinimumGameObject", 10); + ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetOption("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length() - 1) != '/' && dataPath.at(dataPath.length() - 1) != '\\'))