Core/Spawning: Actually check spawn group state before processing a respawn. It feels like that is something that should've been noticed at some point.

Port From (https://github.com/TrinityCore/TrinityCore/commit/5c80f5073493a3d31fd4b463891727beb6fe7eb1)
This commit is contained in:
hondacrx
2021-08-09 09:37:36 -04:00
parent bfd44a494d
commit 85ffed189f
6 changed files with 583 additions and 631 deletions
+66 -64
View File
@@ -443,47 +443,52 @@ namespace Game.Entities
{ {
case DeathState.JustRespawned: case DeathState.JustRespawned:
case DeathState.JustDied: case DeathState.JustDied:
Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {1}", GetGUID().ToString(), m_deathState); Log.outError(LogFilter.Unit, $"Creature ({GetGUID()}) in wrong state: {m_deathState}");
break; break;
case DeathState.Dead: case DeathState.Dead:
{
if (!m_respawnCompatibilityMode)
{ {
long now = GameTime.GetGameTime(); Log.outError(LogFilter.Unit, $"Creature (GUID: {GetGUID().GetCounter()} Entry: {GetEntry()}) in wrong state: DEAD (3)");
if (m_respawnTime <= now)
{
// First check if there are any scripts that object to us respawning
if (!Global.ScriptMgr.CanSpawn(GetSpawnId(), GetEntry(), GetCreatureData(), GetMap()))
{
m_respawnTime = now + RandomHelper.URand(4, 7);
break; // Will be rechecked on next Update call after delay expires
}
ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.Creature, GetMapId(), GetEntry(), m_spawnId);
long linkedRespawnTime = GetMap().GetLinkedRespawnTime(dbtableHighGuid);
if (linkedRespawnTime == 0) // Can respawn
Respawn();
else // the master is dead
{
ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid);
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
SetRespawnTime(Time.Week);
else
{
// else copy time from master and add a little
long baseRespawnTime = Math.Max(linkedRespawnTime, now);
long offset = RandomHelper.URand(5, Time.Minute);
// linked guid can be a boss, uses std::numeric_limits<time_t>::max to never respawn in that instance
// we shall inherit it instead of adding and causing an overflow
if (baseRespawnTime <= long.MaxValue - offset)
m_respawnTime = baseRespawnTime + offset;
else
m_respawnTime = long.MaxValue;
}
SaveRespawnTime(); // also save to DB immediately
}
}
break; break;
} }
long now = GameTime.GetGameTime();
if (m_respawnTime <= now)
{
// Delay respawn if spawn group is not active
if (m_creatureData != null && !GetMap().IsSpawnGroupActive(m_creatureData.spawnGroupData.groupId))
{
m_respawnTime = now + RandomHelper.URand(4, 7);
break; // Will be rechecked on next Update call after delay expires
}
ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.Creature, GetMapId(), GetEntry(), m_spawnId);
long linkedRespawnTime = GetMap().GetLinkedRespawnTime(dbtableHighGuid);
if (linkedRespawnTime == 0) // Can respawn
Respawn();
else // the master is dead
{
ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid);
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
SetRespawnTime(Time.Week);
else
{
// else copy time from master and add a little
long baseRespawnTime = Math.Max(linkedRespawnTime, now);
long offset = RandomHelper.URand(5, Time.Minute);
// linked guid can be a boss, uses std::numeric_limits<time_t>::max to never respawn in that instance
// we shall inherit it instead of adding and causing an overflow
if (baseRespawnTime <= long.MaxValue - offset)
m_respawnTime = baseRespawnTime + offset;
else
m_respawnTime = long.MaxValue;
}
SaveRespawnTime(); // also save to DB immediately
}
}
break;
}
case DeathState.Corpse: case DeathState.Corpse:
base.Update(diff); base.Update(diff);
if (m_deathState != DeathState.Corpse) if (m_deathState != DeathState.Corpse)
@@ -651,30 +656,30 @@ namespace Game.Entities
switch (power) switch (power)
{ {
case PowerType.Focus: case PowerType.Focus:
{ {
// For hunter pets. // For hunter pets.
addvalue = 24 * WorldConfig.GetFloatValue(WorldCfg.RatePowerFocus); addvalue = 24 * WorldConfig.GetFloatValue(WorldCfg.RatePowerFocus);
break; break;
} }
case PowerType.Energy: case PowerType.Energy:
{ {
// For deathknight's ghoul. // For deathknight's ghoul.
addvalue = 20; addvalue = 20;
break; break;
} }
case PowerType.Mana: case PowerType.Mana:
{
// Combat and any controlled creature
if (IsInCombat() || GetCharmerOrOwnerGUID().IsEmpty())
{ {
// Combat and any controlled creature float ManaIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RatePowerMana);
if (IsInCombat() || GetCharmerOrOwnerGUID().IsEmpty()) addvalue = (27.0f / 5.0f + 17.0f) * ManaIncreaseRate;
{
float ManaIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RatePowerMana);
addvalue = (27.0f / 5.0f + 17.0f) * ManaIncreaseRate;
}
else
addvalue = maxValue / 3;
break;
} }
else
addvalue = maxValue / 3;
break;
}
default: default:
return; return;
} }
@@ -3119,7 +3124,7 @@ namespace Game.Entities
CreatureData data = Global.ObjectMgr.GetCreatureData(spawnId); CreatureData data = Global.ObjectMgr.GetCreatureData(spawnId);
if (data == null) if (data == null)
{ {
Log.outError(LogFilter.Sql, "Creature (GUID: {0}) not found in table `creature`, can't load. ", spawnId); Log.outError(LogFilter.Sql, $"Creature (SpawnID: {spawnId}) not found in table `creature`, can't load.");
return false; return false;
} }
@@ -3128,12 +3133,6 @@ namespace Game.Entities
m_creatureData = data; m_creatureData = data;
m_respawnradius = data.spawndist; m_respawnradius = data.spawndist;
m_respawnDelay = (uint)data.spawntimesecs; m_respawnDelay = (uint)data.spawntimesecs;
// Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update)
if (!m_respawnCompatibilityMode && m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, data.Id, data, map))
{
SaveRespawnTime(RandomHelper.URand(4, 7));
return false;
}
if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.Id, data.spawnPoint, data, 0, !m_respawnCompatibilityMode)) if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.Id, data.spawnPoint, data, 0, !m_respawnCompatibilityMode))
return false; return false;
@@ -3146,8 +3145,11 @@ namespace Game.Entities
m_respawnTime = GetMap().GetCreatureRespawnTime(m_spawnId); m_respawnTime = GetMap().GetCreatureRespawnTime(m_spawnId);
// Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update)
if (m_respawnCompatibilityMode && m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, GetEntry(), GetCreatureData(), map)) if (m_respawnTime == 0 && !map.IsSpawnGroupActive(data.spawnGroupData.groupId))
{
Cypher.Assert(m_respawnCompatibilityMode, $"Creature (SpawnID {spawnId}) trying to load in inactive spawn group {data.spawnGroupData.name}.");
m_respawnTime = GameTime.GetGameTime() + RandomHelper.URand(4, 7); m_respawnTime = GameTime.GetGameTime() + RandomHelper.URand(4, 7);
}
if (m_respawnTime != 0) // respawn on UpdateLoadCreatureFromDB if (m_respawnTime != 0) // respawn on UpdateLoadCreatureFromDB
{ {
+1 -1
View File
@@ -249,7 +249,7 @@ namespace Game.Maps
if (doSpawn) if (doSpawn)
instance.SpawnGroupSpawn(groupId, instance); instance.SpawnGroupSpawn(groupId, instance);
else // otherwise, set it as inactive so it no longer respawns (but don't despawn it) else // otherwise, set it as inactive so it no longer respawns (but don't despawn it)
instance.SetSpawnGroupActive(groupId, false); instance.SetSpawnGroupInactive(groupId);
} }
} }
+504 -505
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -110,14 +110,6 @@ namespace Game.Maps
continue; continue;
} }
} }
// If script is blocking spawn, don't spawn but queue for a re-check in a little bit
if (!group.flags.HasFlag(SpawnGroupFlags.CompatibilityMode) && !Global.ScriptMgr.CanSpawn(guid, cdata.Id, cdata, map))
{
map.SaveRespawnTime(SpawnObjectType.Creature, guid, cdata.Id, GameTime.GetGameTime() + RandomHelper.URand(4, 7), map.GetZoneId(PhasingHandler.EmptyPhaseShift, cdata.spawnPoint), GridDefines.ComputeGridCoord(cdata.spawnPoint.GetPositionX(), cdata.spawnPoint.GetPositionY()).GetId(), false);
obj.Dispose();
continue;
}
} }
else if (obj.IsTypeId(TypeId.GameObject)) else if (obj.IsTypeId(TypeId.GameObject))
{ {
-3
View File
@@ -333,9 +333,6 @@ namespace Game.Scripting
public override bool IsDatabaseBound() { return true; } public override bool IsDatabaseBound() { return true; }
// Called when the creature tries to spawn. Return false to block spawn and re-evaluate on next tick.
public virtual bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate baseTemplate, CreatureTemplate actTemplate, CreatureData cData, Map map) { return true; }
// Called when a CreatureAI object is needed for the creature. // Called when a CreatureAI object is needed for the creature.
public virtual CreatureAI GetAI(Creature creature) { return null; } public virtual CreatureAI GetAI(Creature creature) { return null; }
} }
-38
View File
@@ -681,44 +681,6 @@ namespace Game.Scripting
return RunScriptRet<ItemScript>(tmpscript => tmpscript.OnCastItemCombatSpell(player, victim, spellInfo, item), item.GetScriptId()); return RunScriptRet<ItemScript>(tmpscript => tmpscript.OnCastItemCombatSpell(player, victim, spellInfo, item), item.GetScriptId());
} }
//CreatureScript
public bool CanSpawn(ulong spawnId, uint entry, CreatureData cData, Map map)
{
Cypher.Assert(map != null);
CreatureTemplate baseTemplate = Global.ObjectMgr.GetCreatureTemplate(entry);
Cypher.Assert(baseTemplate != null);
// find out which template we'd be using
CreatureTemplate actTemplate = null;
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(map.GetDifficultyID());
while (actTemplate == null && difficultyEntry != null)
{
int idx = CreatureTemplate.DifficultyIDToDifficultyEntryIndex(difficultyEntry.Id);
if (idx == -1)
break;
if (baseTemplate.DifficultyEntry[idx] != 0)
{
actTemplate = Global.ObjectMgr.GetCreatureTemplate(baseTemplate.DifficultyEntry[idx]);
break;
}
if (difficultyEntry.FallbackDifficultyID == 0)
break;
difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID);
}
if (actTemplate == null)
actTemplate = baseTemplate;
uint scriptId = baseTemplate.ScriptID;
if (cData != null && cData.ScriptId != 0)
scriptId = cData.ScriptId;
return RunScriptRet<CreatureScript, bool>(p => p.CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map), scriptId, true);
}
public CreatureAI GetCreatureAI(Creature creature) public CreatureAI GetCreatureAI(Creature creature)
{ {
Cypher.Assert(creature != null); Cypher.Assert(creature != null);