Dynamic Creature/Go spawning

Port From (https://github.com/TrinityCore/TrinityCore/commit/03b125e6d1947258316c931499746696a95aded2)
This commit is contained in:
hondacrx
2020-08-23 21:52:32 -04:00
parent 8fc9c45d50
commit 15ae7a7c66
45 changed files with 3925 additions and 1963 deletions
+19 -4
View File
@@ -1079,11 +1079,26 @@ namespace Framework.Constants
DebugSceneObjectList = 5068,
DebugSceneObjectDetail = 5069,
NpcinfoUnitFieldFlags2 = 5070,
NpcinfoUnitFieldFlags3 = 5071,
NpcinfoNpcFlags = 5072,
// Strings Added For DynamicSpawning
SpawninfoGroupId = 5070,
SpawninfoCompatibilityMode = 5071,
SpawninfoGuidinfo = 5072,
SpawninfoSpawnidLocation = 5073,
SpawninfoDistancefromplayer = 5074,
SpawngroupBadgroup = 5075,
SpawngroupSpawncount = 5076,
ListRespawnsRange = 5077,
ListRespawnsZone = 5078,
ListRespawnsListheader = 5079,
ListRespawnsOverdue = 5080,
ListRespawnsCreatures = 5081,
ListRespawnsGameobjects = 5082,
// Room For More Trinity Strings 5073-9999
NpcinfoUnitFieldFlags2 = 5084,
NpcinfoUnitFieldFlags3 = 5085,
NpcinfoNpcFlags = 5086,
// Room For More Trinity Strings 5087-6603
// Level Requirement Notifications
SayReq = 6604,
+31
View File
@@ -20,6 +20,8 @@ namespace Framework.Constants
{
public class MapConst
{
public const uint InvalidZone = 0xFFFFFFFF;
//Grids
public const int MaxGrids = 64;
public const float SizeofGrids = 533.33333f;
@@ -200,4 +202,33 @@ namespace Framework.Constants
All = Vmap | Gobject
}
public enum SpawnObjectType
{
Creature = 0,
GameObject = 1,
Max
}
public enum SpawnObjectTypeMask
{
Creature = (1 << SpawnObjectType.Creature),
GameObject = (1 << SpawnObjectType.GameObject),
All = (1 << SpawnObjectType.Max) - 1
}
[Flags]
public enum SpawnGroupFlags
{
None = 0x00,
System = 0x01,
CompatibilityMode = 0x02,
ManualSpawn = 0x04,
DynamicSpawnRate = 0x08,
EscortQuestNpc = 0x10,
All = (System | CompatibilityMode | ManualSpawn | DynamicSpawnRate | EscortQuestNpc)
}
}
+11
View File
@@ -1316,6 +1316,17 @@ namespace Framework.Constants
RealmZone,
ResetDuelCooldowns,
ResetDuelHealthMana,
RespawnDynamicEscortNpc,
RespawnDynamicMinimumCreature,
RespawnDynamicMinimumGameObject,
RespawnDynamicMode,
RespawnDynamicRateCreature,
RespawnDynamicRateGameobject,
RespawnGuidAlertLevel,
RespawnGuidWarnLevel,
RespawnGuidWarningFrequency,
RespawnMinCheckIntervalMs,
RespawnRestartQuietTime,
RestrictedLfgChannel,
SaveRespawnTimeImmediately,
SessionAddDelay,
+15 -1
View File
@@ -13,7 +13,9 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
*/
using System;
namespace Framework.Constants
{
@@ -114,6 +116,15 @@ namespace Framework.Constants
End = 3
}
[Flags]
public enum SmartAiSpawnFlags
{
None = 0x00,
IgnoreRespawn = 0x01,
ForceSpawn = 0x02,
NosaveRespawn = 0x04,
}
public enum SmartAITemplate
{
Basic = 0, //nothing is preset
@@ -125,6 +136,7 @@ namespace Framework.Constants
End = 6
}
[Flags]
public enum SmartCastFlags
{
InterruptPrevious = 0x01, //Interrupt any spell casting
@@ -361,6 +373,8 @@ namespace Framework.Constants
PlayAnimkit = 128,
ScenePlay = 129, // sceneId
SceneCancel = 130, // sceneId
SpawnSpawngroup = 131, // Group ID, min secs, max secs, spawnflags
DespawnSpawngroup = 132, // Group ID, min secs, max secs, spawnflags
// 131 - 134 : 3.3.5 reserved
PlayCinematic = 135, // reserved for future uses
SetMovementSpeed = 136, // movementType, speedInteger, speedFraction
@@ -89,6 +89,7 @@ namespace Framework.Database
PrepareStatement(WorldStatements.DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?");
PrepareStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?");
PrepareStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?");
PrepareStatement(WorldStatements.DEL_SPAWNGROUP_MEMBER, "DELETE FROM spawn_group WHERE spawnType = ? AND spawnId = ?");
PrepareStatement(WorldStatements.SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS, "SELECT AchievementRequired FROM guild_rewards_req_achievements WHERE ItemID = ?");
}
}
@@ -164,6 +165,7 @@ namespace Framework.Database
DEL_DISABLES,
UPD_CREATURE_ZONE_AREA_DATA,
UPD_GAMEOBJECT_ZONE_AREA_DATA,
DEL_SPAWNGROUP_MEMBER,
SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS,
MAX_WORLDDATABASE_STATEMENTS
+9
View File
@@ -474,6 +474,15 @@ namespace Game.AI
// Object destruction is handled by Unit::RemoveCharmedBy
public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; }
/// <summary>
/// Should return true if the NPC is target of an escort quest
/// If onlyIfActive is set, should return true only if the escort quest is currently active
/// </summary>
/// <param name="onlyIfActive"></param>
/// <returns></returns>
public virtual bool IsEscortNPC(bool onlyIfActive) { return false; }
List<AreaBoundary> GetBoundary() { return _boundary; }
bool MoveInLineOfSight_locked;
+7 -5
View File
@@ -495,7 +495,7 @@ namespace Game.AI
_events.Reset();
summons.DespawnAll();
_scheduler.CancelAll();
if (instance != null)
if (instance != null && instance.GetBossState(_bossId) != EncounterState.Done)
instance.SetBossState(_bossId, EncounterState.NotStarted);
}
@@ -576,12 +576,14 @@ namespace Game.AI
DoMeleeAttackIfReady();
}
public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null)
public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null) { _DespawnAtEvade(TimeSpan.FromSeconds(delayToRespawn), who); }
public void _DespawnAtEvade(TimeSpan delayToRespawn, Creature who = null)
{
if (delayToRespawn < 2)
if (delayToRespawn < TimeSpan.FromSeconds(2))
{
Log.outError(LogFilter.Scripts, "_DespawnAtEvade called with delay of {0} seconds, defaulting to 2.", delayToRespawn);
delayToRespawn = 2;
delayToRespawn = TimeSpan.FromSeconds(2);
}
if (!who)
@@ -595,7 +597,7 @@ namespace Game.AI
return;
}
who.DespawnOrUnsummon(0, TimeSpan.FromSeconds(delayToRespawn));
who.DespawnOrUnsummon(0, delayToRespawn);
if (instance != null && who == me)
instance.SetBossState(_bossId, EncounterState.Fail);
+44 -2
View File
@@ -18,6 +18,7 @@
using Framework.Constants;
using Game.Entities;
using Game.Groups;
using Game.Maps;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -234,13 +235,17 @@ namespace Game.AI
return;
}
if (m_bCanInstantRespawn)
if (m_bCanInstantRespawn && !WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc))
{
me.SetDeathState(DeathState.JustDied);
me.Respawn();
}
else
{
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc))
me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true);
me.DespawnOrUnsummon();
}
return;
}
@@ -274,11 +279,18 @@ namespace Game.AI
{
Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found");
if (m_bCanInstantRespawn)
bool isEscort = false;
CreatureData cdata = me.GetCreatureData();
if (cdata != null)
isEscort = (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && cdata.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc));
if (m_bCanInstantRespawn && !isEscort)
{
me.SetDeathState(DeathState.JustDied);
me.Respawn();
}
else if (m_bCanInstantRespawn && isEscort)
me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true);
else
me.DespawnOrUnsummon();
@@ -391,6 +403,25 @@ namespace Game.AI
/// todo get rid of this many variables passed in function.
public void Start(bool isActiveAttacker = true, bool run = false, ObjectGuid playerGUID = default, Quest quest = null, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true)
{
// Queue respawn from the point it starts
Map map = me.GetMap();
if (map != null)
{
CreatureData cdata = me.GetCreatureData();
if (cdata != null)
{
SpawnGroupTemplateData groupdata = cdata.spawnGroupData;
if (groupdata != null)
{
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && groupdata.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc) && map.GetCreatureRespawnTime(me.GetSpawnId()) == 0)
{
me.SetRespawnTime(me.GetRespawnDelay());
me.SaveRespawnTime();
}
}
}
}
if (me.GetVictim())
{
Log.outError(LogFilter.Server, "TSCR ERROR: EscortAI (script: {0}, creature entry: {1}) attempts to Start while in combat", me.GetScriptName(), me.GetEntry());
@@ -536,6 +567,17 @@ namespace Game.AI
return false;
}
public override bool IsEscortNPC(bool onlyIfActive)
{
if (!onlyIfActive)
return true;
if (!GetEventStarterGUID().IsEmpty())
return true;
return false;
}
public virtual void WaypointReached(uint pointId) { }
public virtual void WaypointStart(uint pointId) { }
+21 -9
View File
@@ -129,39 +129,39 @@ namespace Game.AI
continue;
}
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creature.id);
CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creature.Id);
if (creatureInfo == null)
{
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading.");
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.Id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading.");
continue;
}
if (creatureInfo.AIName != "SmartAI")
{
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.id}) guid ({-temp.entryOrGuid}) is not using SmartAI, skipped loading.");
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.Id}) guid ({-temp.entryOrGuid}) is not using SmartAI, skipped loading.");
continue;
}
break;
}
case SmartScriptType.GameObject:
{
GameObjectData gameObject = Global.ObjectMgr.GetGOData((ulong)-temp.entryOrGuid);
GameObjectData gameObject = Global.ObjectMgr.GetGameObjectData((ulong)-temp.entryOrGuid);
if (gameObject == null)
{
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject guid ({-temp.entryOrGuid}) does not exist, skipped loading.");
continue;
}
GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObject.id);
GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObject.Id);
if (gameObjectInfo == null)
{
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading.");
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading.");
continue;
}
if (gameObjectInfo.AIName != "SmartGameObjectAI")
{
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.id}) guid ({-temp.entryOrGuid}) is not using SmartGameObjectAI, skipped loading.");
Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.entryOrGuid}) is not using SmartGameObjectAI, skipped loading.");
continue;
}
break;
@@ -758,7 +758,7 @@ namespace Game.AI
return false;
}
if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGOData(e.Event.distance.guid) == null)
if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGameObjectData(e.Event.distance.guid) == null)
{
Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid {0}, skipped.", e.Event.distance.guid);
return false;
@@ -1376,6 +1376,8 @@ namespace Game.AI
case SmartActions.TriggerRandomTimedEvent:
case SmartActions.SetCounter:
case SmartActions.RemoveAllGameobjects:
case SmartActions.SpawnSpawngroup:
case SmartActions.DespawnSpawngroup:
break;
default:
Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id);
@@ -1423,7 +1425,7 @@ namespace Game.AI
return false;
}
else
entry = data.id;
entry = data.Id;
}
else
entry = (uint)e.entryOrGuid;
@@ -2368,6 +2370,9 @@ namespace Game.AI
[FieldOffset(4)]
public DisableEvade disableEvade;
[FieldOffset(4)]
public GroupSpawn groupSpawn;
[FieldOffset(4)]
public AuraType auraType;
@@ -2854,6 +2859,13 @@ namespace Game.AI
{
public uint disable;
}
public struct GroupSpawn
{
public uint groupId;
public uint minDelay;
public uint maxDelay;
public uint spawnflags;
}
public struct AuraType
{
public uint type;
@@ -2051,6 +2051,91 @@ namespace Game.AI
target.ToCreature().SetCorpseDelay(e.Action.corpseDelay.timer);
break;
}
case SmartActions.SpawnSpawngroup:
{
if (e.Action.groupSpawn.minDelay == 0 && e.Action.groupSpawn.maxDelay == 0)
{
bool ignoreRespawn = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.IgnoreRespawn) != 0);
bool force = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.ForceSpawn) != 0);
// Instant spawn
Global.ObjectMgr.SpawnGroupSpawn(e.Action.groupSpawn.groupId, GetBaseObject().GetMap(), ignoreRespawn, force);
}
else
{
// Delayed spawn (use values from parameter to schedule event to call us back
SmartEvent ne = new SmartEvent();
ne.type = SmartEvents.Update;
ne.event_chance = 100;
ne.minMaxRepeat.min = e.Action.groupSpawn.minDelay;
ne.minMaxRepeat.max = e.Action.groupSpawn.maxDelay;
ne.minMaxRepeat.repeatMin = 0;
ne.minMaxRepeat.repeatMax = 0;
ne.event_flags = 0;
ne.event_flags |= SmartEventFlags.NotRepeatable;
SmartAction ac = new SmartAction();
ac.type = SmartActions.SpawnSpawngroup;
ac.groupSpawn.groupId = e.Action.groupSpawn.groupId;
ac.groupSpawn.minDelay = 0;
ac.groupSpawn.maxDelay = 0;
ac.groupSpawn.spawnflags = e.Action.groupSpawn.spawnflags;
ac.timeEvent.id = e.Action.timeEvent.id;
SmartScriptHolder ev = new SmartScriptHolder();
ev.Event = ne;
ev.event_id = e.event_id;
ev.Target = e.Target;
ev.Action = ac;
InitTimer(ev);
mStoredEvents.Add(ev);
}
break;
}
case SmartActions.DespawnSpawngroup:
{
if (e.Action.groupSpawn.minDelay == 0 && e.Action.groupSpawn.maxDelay == 0)
{
bool deleteRespawnTimes = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.NosaveRespawn) != 0);
// Instant spawn
Global.ObjectMgr.SpawnGroupDespawn(e.Action.groupSpawn.groupId, GetBaseObject().GetMap(), deleteRespawnTimes);
}
else
{
// Delayed spawn (use values from parameter to schedule event to call us back
SmartEvent ne = new SmartEvent();
ne.type = SmartEvents.Update;
ne.event_chance = 100;
ne.minMaxRepeat.min = e.Action.groupSpawn.minDelay;
ne.minMaxRepeat.max = e.Action.groupSpawn.maxDelay;
ne.minMaxRepeat.repeatMin = 0;
ne.minMaxRepeat.repeatMax = 0;
ne.event_flags = 0;
ne.event_flags |= SmartEventFlags.NotRepeatable;
SmartAction ac = new SmartAction();
ac.type = SmartActions.DespawnSpawngroup;
ac.groupSpawn.groupId = e.Action.groupSpawn.groupId;
ac.groupSpawn.minDelay = 0;
ac.groupSpawn.maxDelay = 0;
ac.groupSpawn.spawnflags = e.Action.groupSpawn.spawnflags;
ac.timeEvent.id = e.Action.timeEvent.id;
SmartScriptHolder ev = new SmartScriptHolder();
ev.Event = ne;
ev.event_id = e.event_id;
ev.Target = e.Target;
ev.Action = ac;
InitTimer(ev);
mStoredEvents.Add(ev);
}
break;
}
case SmartActions.DisableEvade:
{
if (!IsSmart())
@@ -201,7 +201,7 @@ namespace Game.Chat
if (gameObjectInfo == null)
continue;
handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gameObjectInfo.name, x, y, z, mapId);
handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gameObjectInfo.name, x, y, z, mapId, "", "");
++count;
} while (result.NextRow());
@@ -412,10 +412,10 @@ namespace Game.Chat
if (!ulong.TryParse(cValue, out ulong guidLow))
return false;
GameObjectData data = Global.ObjectMgr.GetGOData(guidLow);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guidLow);
if (data == null)
return false;
entry = data.id;
entry = data.Id;
}
else
{
@@ -427,15 +427,45 @@ namespace Game.Chat
if (gameObjectInfo == null)
return false;
GameObject thisGO = null;
if (handler.GetSession().GetPlayer())
thisGO = handler.GetSession().GetPlayer().FindNearestGameObject(entry, 30);
else if (handler.GetSelectedObject() != null && handler.GetSelectedObject().IsTypeId(TypeId.GameObject))
thisGO = handler.GetSelectedObject().ToGameObject();
GameObjectTypes type = gameObjectInfo.type;
uint displayId = gameObjectInfo.displayId;
string name = gameObjectInfo.name;
uint lootId = gameObjectInfo.GetLootId();
// If we have a real object, send some info about it
if (thisGO != null)
{
handler.SendSysMessage(CypherStrings.SpawninfoGuidinfo, thisGO.GetGUID().ToString());
handler.SendSysMessage(CypherStrings.SpawninfoSpawnidLocation, thisGO.GetSpawnId(), thisGO.GetPositionX(), thisGO.GetPositionY(), thisGO.GetPositionZ());
Player player = handler.GetSession().GetPlayer();
if (player != null)
{
Position playerPos = player.GetPosition();
float dist = thisGO.GetExactDist(playerPos);
handler.SendSysMessage(CypherStrings.SpawninfoDistancefromplayer, dist);
}
}
handler.SendSysMessage(CypherStrings.GoinfoEntry, entry);
handler.SendSysMessage(CypherStrings.GoinfoType, type);
handler.SendSysMessage(CypherStrings.GoinfoLootid, lootId);
handler.SendSysMessage(CypherStrings.GoinfoDisplayid, displayId);
WorldObject obj = handler.GetSelectedObject();
if (obj != null)
{
if (obj.IsGameObject() && obj.ToGameObject().GetGameObjectData() != null && obj.ToGameObject().GetGameObjectData().spawnGroupData.groupId != 0)
{
SpawnGroupTemplateData groupData = obj.ToGameObject().GetGameObjectData().spawnGroupData;
handler.SendSysMessage(CypherStrings.SpawninfoGroupId, groupData.name, groupData.groupId, groupData.flags, groupData.isActive);
}
if (obj.IsGameObject())
handler.SendSysMessage(CypherStrings.SpawninfoCompatibilityMode, obj.ToGameObject().GetRespawnCompatibilityMode());
}
handler.SendSysMessage(CypherStrings.GoinfoName, name);
handler.SendSysMessage(CypherStrings.GoinfoSize, gameObjectInfo.size);
@@ -508,7 +538,7 @@ namespace Game.Chat
return false;
// TODO: is it really necessary to add both the real and DB table guid here ?
Global.ObjectMgr.AddGameObjectToGrid(spawnId, Global.ObjectMgr.GetGOData(spawnId));
Global.ObjectMgr.AddGameObjectToGrid(spawnId, Global.ObjectMgr.GetGameObjectData(spawnId));
handler.SendSysMessage(CypherStrings.GameobjectAdd, objectId, objectInfo.name, spawnId, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ());
return true;
}
+5 -16
View File
@@ -202,28 +202,17 @@ namespace Game.Chat.Commands
if (!ulong.TryParse(id, out ulong guidLow) || guidLow == 0)
return false;
float x, y, z, o;
uint mapId;
// by DB guid
GameObjectData goData = Global.ObjectMgr.GetGOData(guidLow);
if (goData != null)
{
x = goData.posX;
y = goData.posY;
z = goData.posZ;
o = goData.orientation;
mapId = goData.mapid;
}
else
GameObjectData goData = Global.ObjectMgr.GetGameObjectData(guidLow);
if (goData == null)
{
handler.SendSysMessage(CypherStrings.CommandGoobjnotfound);
return false;
}
if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId))
if (!GridDefines.IsValidMapCoord(goData.spawnPoint) || Global.ObjectMgr.IsTransportMap(goData.spawnPoint.GetMapId()))
{
handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId);
handler.SendSysMessage(CypherStrings.InvalidTargetCoord, goData.spawnPoint.GetPositionX(), goData.spawnPoint.GetPositionY(), goData.spawnPoint.GetMapId());
return false;
}
@@ -237,7 +226,7 @@ namespace Game.Chat.Commands
else
player.SaveRecallPosition();
player.TeleportTo(mapId, x, y, z, o);
player.TeleportTo(goData.spawnPoint);
return true;
}
+139 -4
View File
@@ -18,7 +18,9 @@
using Framework.Constants;
using Framework.Database;
using Framework.IO;
using Game.DataStorage;
using Game.Entities;
using Game.Maps;
using Game.Spells;
using System.Collections.Generic;
@@ -128,11 +130,39 @@ namespace Game.Chat.Commands
float y = result.Read<float>(2);
float z = result.Read<float>(3);
ushort mapId = result.Read<ushort>(4);
bool liveFound = false;
// Get map (only support base map from console)
Map thisMap;
if (handler.GetSession() != null)
handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId);
thisMap = handler.GetSession().GetPlayer().GetMap();
else
handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId);
thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId);
// If map found, try to find active version of this creature
if (thisMap)
{
var creBounds = thisMap.GetCreatureBySpawnIdStore().LookupByKey(guid);
if (!creBounds.Empty())
{
foreach (var creature in creBounds)
{
if (handler.GetSession())
handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " ");
else
handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " ");
}
liveFound = true;
}
}
if (!liveFound)
{
if (handler.GetSession())
handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, "", "");
else
handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId, "", "");
}
}
while (result.NextRow());
}
@@ -495,11 +525,39 @@ namespace Game.Chat.Commands
float z = result.Read<float>(3);
ushort mapId = result.Read<ushort>(4);
uint entry = result.Read<uint>(5);
bool liveFound = false;
// Get map (only support base map from console)
Map thisMap;
if (handler.GetSession() != null)
handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId);
thisMap = handler.GetSession().GetPlayer().GetMap();
else
handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId);
thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId);
// If map found, try to find active version of this object
if (thisMap)
{
var goBounds = thisMap.GetGameObjectBySpawnIdStore().LookupByKey(guid);
if (!goBounds.Empty())
{
foreach (var go in goBounds)
{
if (handler.GetSession())
handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " ");
else
handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " ");
}
liveFound = true;
}
}
if (!liveFound)
{
if (handler.GetSession())
handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, "", "");
else
handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId, "", "");
}
}
while (result.NextRow());
}
@@ -509,6 +567,77 @@ namespace Game.Chat.Commands
return true;
}
[Command("respawns", RBACPermissions.CommandListRespawns)]
static bool HandleListRespawnsCommand(StringArguments args, CommandHandler handler)
{
// We need a player
Player player = handler.GetSession().GetPlayer();
if (player == null)
return false;
// And we need a map
Map map = player.GetMap();
if (map == null)
return false;
uint range = 0;
if (!args.Empty())
range = args.NextUInt32();
List<RespawnInfo> respawns = new List<RespawnInfo>();
Locale locale = handler.GetSession().GetSessionDbcLocale();
string stringOverdue = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsOverdue, locale);
string stringCreature = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsCreatures, locale);
string stringGameobject = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsGameobjects, locale);
uint zoneId = player.GetZoneId();
if (range != 0)
handler.SendSysMessage(CypherStrings.ListRespawnsRange, stringCreature, range);
else
handler.SendSysMessage(CypherStrings.ListRespawnsZone, stringCreature, GetZoneName(zoneId, handler.GetSessionDbcLocale()), zoneId);
handler.SendSysMessage(CypherStrings.ListRespawnsListheader);
map.GetRespawnInfo(respawns, SpawnObjectTypeMask.Creature, range != 0 ? 0 : zoneId);
foreach (RespawnInfo ri in respawns)
{
CreatureData data = Global.ObjectMgr.GetCreatureData(ri.spawnId);
if (data == null)
continue;
if (range != 0 && !player.IsInDist(data.spawnPoint, range))
continue;
uint gridY = ri.gridId / MapConst.MaxGrids;
uint gridX = ri.gridId % MapConst.MaxGrids;
string respawnTime = ri.respawnTime > Time.UnixTime ? Time.secsToTimeString((ulong)(ri.respawnTime - Time.UnixTime), true) : stringOverdue;
handler.SendSysMessage($"{ri.spawnId} | {ri.entry} | [{gridX},{gridY}] | {GetZoneName(ri.zoneId, handler.GetSessionDbcLocale())} ({ri.zoneId}) | {(data.spawnGroupData.isActive ? respawnTime : "inactive")}");
}
respawns.Clear();
if (range != 0)
handler.SendSysMessage(CypherStrings.ListRespawnsRange, stringGameobject, range);
else
handler.SendSysMessage(CypherStrings.ListRespawnsZone, stringGameobject, GetZoneName(zoneId, handler.GetSessionDbcLocale()), zoneId);
handler.SendSysMessage(CypherStrings.ListRespawnsListheader);
map.GetRespawnInfo(respawns, SpawnObjectTypeMask.GameObject, range != 0 ? 0 : zoneId);
foreach (RespawnInfo ri in respawns)
{
GameObjectData data = Global.ObjectMgr.GetGameObjectData(ri.spawnId);
if (data == null)
continue;
if (range != 0 && !player.IsInDist(data.spawnPoint, range))
continue;
uint gridY = ri.gridId / MapConst.MaxGrids;
uint gridX = ri.gridId % MapConst.MaxGrids;
string respawnTime = ri.respawnTime > Time.UnixTime ? Time.secsToTimeString((ulong)(ri.respawnTime - Time.UnixTime), true) : stringOverdue;
handler.SendSysMessage($"{ri.spawnId} | {ri.entry} | [{gridX},{gridY}] | {GetZoneName(ri.zoneId, handler.GetSessionDbcLocale())} ({ri.zoneId}) | {(data.spawnGroupData.isActive ? respawnTime : "inactive")}");
}
return true;
}
[Command("scenes", RBACPermissions.CommandListScenes)]
static bool HandleListScenesCommand(StringArguments args, CommandHandler handler)
{
@@ -531,5 +660,11 @@ namespace Game.Chat.Commands
return true;
}
static string GetZoneName(uint zoneId, Locale locale)
{
AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(zoneId);
return zoneEntry != null ? zoneEntry.AreaName[locale] : "<unknown zone>";
}
}
}
File diff suppressed because it is too large Load Diff
+140 -61
View File
@@ -33,6 +33,41 @@ namespace Game.Chat
[CommandGroup("npc", RBACPermissions.CommandNpc)]
class NPCCommands
{
[Command("despawngroup", RBACPermissions.CommandNpcDespawngroup)]
static bool HandleNpcDespawnGroup(StringArguments args, CommandHandler handler)
{
if (args.Empty())
return false;
bool deleteRespawnTimes = false;
uint groupId = 0;
// Decode arguments
string arg = args.NextString();
while (!arg.IsEmpty())
{
string thisArg = arg.ToLower();
if (thisArg == "removerespawntime")
deleteRespawnTimes = true;
else if (thisArg.IsEmpty() || !thisArg.IsNumber())
return false;
else
groupId = uint.Parse(thisArg);
arg = args.NextString();
}
Player player = handler.GetSession().GetPlayer();
if (!Global.ObjectMgr.SpawnGroupDespawn(groupId, player.GetMap(), deleteRespawnTimes))
{
handler.SendSysMessage(CypherStrings.SpawngroupBadgroup, groupId);
return false;
}
return true;
}
[Command("evade", RBACPermissions.CommandNpcEvade)]
static bool HandleNpcEvadeCommand(StringArguments args, CommandHandler handler)
{
@@ -96,13 +131,21 @@ namespace Game.Chat
uint nativeid = target.GetNativeDisplayId();
uint Entry = target.GetEntry();
long curRespawnDelay = target.GetRespawnTimeEx() - Time.UnixTime;
long curRespawnDelay = target.GetRespawnCompatibilityMode() ? target.GetRespawnTimeEx() - Time.UnixTime : target.GetMap().GetCreatureRespawnTime(target.GetSpawnId()) - Time.UnixTime;
if (curRespawnDelay < 0)
curRespawnDelay = 0;
string curRespawnDelayStr = Time.secsToTimeString((ulong)curRespawnDelay, true);
string defRespawnDelayStr = Time.secsToTimeString(target.GetRespawnDelay(), true);
handler.SendSysMessage(CypherStrings.NpcinfoChar, target.GetSpawnId(), target.GetGUID().ToString(), faction, npcflags, Entry, displayid, nativeid);
if (target.GetCreatureData() != null && target.GetCreatureData().spawnGroupData.groupId != 0)
{
SpawnGroupTemplateData groupData = target.GetCreatureData().spawnGroupData;
if (groupData != null)
handler.SendSysMessage(CypherStrings.SpawninfoGroupId, groupData.name, groupData.groupId, groupData.flags, groupData.isActive);
}
handler.SendSysMessage(CypherStrings.SpawninfoCompatibilityMode, target.GetRespawnCompatibilityMode());
handler.SendSysMessage(CypherStrings.NpcinfoLevel, target.GetLevel());
handler.SendSysMessage(CypherStrings.NpcinfoEquipment, target.GetCurrentEquipmentId(), target.GetOriginalEquipmentId());
handler.SendSysMessage(CypherStrings.NpcinfoHealth, target.GetCreateHealth(), target.GetMaxHealth(), target.GetHealth());
@@ -161,71 +204,60 @@ namespace Game.Chat
ulong lowguid;
Creature creature = handler.GetSelectedCreature();
if (!creature)
Player player = handler.GetSession().GetPlayer();
if (player == null)
return false;
if (creature != null)
lowguid = creature.GetSpawnId();
else
{
// number or [name] Shift-click form |color|Hcreature:creature_guid|h[name]|h|r
string cId = handler.ExtractKeyFromLink(args, "Hcreature");
if (string.IsNullOrEmpty(cId))
if (cId.IsEmpty())
return false;
if (!ulong.TryParse(cId, out lowguid))
return false;
}
// Attempting creature load from DB data
CreatureData data = Global.ObjectMgr.GetCreatureData(lowguid);
if (data == null)
{
handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid);
return false;
}
uint map_id = data.mapid;
if (handler.GetSession().GetPlayer().GetMapId() != map_id)
{
handler.SendSysMessage(CypherStrings.CommandCreatureatsamemap, lowguid);
return false;
}
}
else
if (data == null)
{
lowguid = creature.GetSpawnId();
handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid);
return false;
}
float x = handler.GetPlayer().GetPositionX();
float y = handler.GetPlayer().GetPositionY();
float z = handler.GetPlayer().GetPositionZ();
float o = handler.GetPlayer().GetOrientation();
if (creature)
if (player.GetMapId() != data.spawnPoint.GetMapId())
{
CreatureData data = Global.ObjectMgr.GetCreatureData(creature.GetSpawnId());
if (data != null)
{
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
}
creature.UpdatePosition(x, y, z, o);
creature.GetMotionMaster().Initialize();
if (creature.IsAlive()) // dead creature will reset movement generator at respawn
{
creature.SetDeathState(DeathState.JustDied);
creature.Respawn();
}
handler.SendSysMessage(CypherStrings.CommandCreatureatsamemap, lowguid);
return false;
}
data.spawnPoint.Relocate(player);
// update position in DB
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_POSITION);
stmt.AddValue(0, x);
stmt.AddValue(1, y);
stmt.AddValue(2, z);
stmt.AddValue(3, o);
stmt.AddValue(0, player.GetPositionX());
stmt.AddValue(1, player.GetPositionY());
stmt.AddValue(2, player.GetPositionZ());
stmt.AddValue(3, player.GetOrientation());
stmt.AddValue(4, lowguid);
DB.World.Execute(stmt);
// respawn selected creature at the new location
if (creature)
{
if (creature.IsAlive())
creature.SetDeathState(DeathState.JustDied);
creature.Respawn(true);
if (!creature.GetRespawnCompatibilityMode())
creature.AddObjectToRemoveList();
}
handler.SendSysMessage(CypherStrings.CommandCreaturemoved);
return true;
}
@@ -264,7 +296,7 @@ namespace Game.Chat
if (creatureTemplate == null)
continue;
handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, creatureTemplate.Name, x, y, z, mapId);
handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, creatureTemplate.Name, x, y, z, mapId, "", "");
++count;
}
@@ -386,6 +418,49 @@ namespace Game.Chat
return true;
}
[Command("spawngroup", RBACPermissions.CommandNpcSpawngroup)]
static bool HandleNpcSpawnGroup(StringArguments args, CommandHandler handler)
{
if (args.Empty())
return false;
bool ignoreRespawn = false;
bool force = false;
uint groupId = 0;
// Decode arguments
string arg = args.NextString();
while (!arg.IsEmpty())
{
string thisArg = arg.ToLower();
if (thisArg == "ignorerespawn")
ignoreRespawn = true;
else if (thisArg == "force")
force = true;
else if (thisArg.IsEmpty() || !thisArg.IsNumber())
return false;
else
groupId = uint.Parse(thisArg);
arg = args.NextString();
}
Player player = handler.GetSession().GetPlayer();
List<WorldObject> creatureList = new List<WorldObject>();
if (!Global.ObjectMgr.SpawnGroupSpawn(groupId, player.GetMap(), ignoreRespawn, force, creatureList))
{
handler.SendSysMessage(CypherStrings.SpawngroupBadgroup, groupId);
return false;
}
handler.SendSysMessage(CypherStrings.SpawngroupSpawncount, creatureList.Count);
foreach (WorldObject obj in creatureList)
handler.SendSysMessage($"{obj.GetName()} ({obj.GetGUID()})");
return true;
}
[Command("tame", RBACPermissions.CommandNpcTame)]
static bool HandleNpcTameCommand(StringArguments args, CommandHandler handler)
{
@@ -581,13 +656,11 @@ namespace Game.Chat
{
ulong guid = map.GenerateLowGuid(HighGuid.Creature);
CreatureData data = Global.ObjectMgr.NewOrExistCreatureData(guid);
data.id = id;
data.posX = chr.GetTransOffsetX();
data.posY = chr.GetTransOffsetY();
data.posZ = chr.GetTransOffsetZ();
data.orientation = chr.GetTransOffsetO();
data.spawnId = guid;
data.Id = id;
data.spawnPoint.Relocate(chr.GetTransOffsetX(), chr.GetTransOffsetY(), chr.GetTransOffsetZ(), chr.GetTransOffsetO());
// @todo: add phases
Creature _creature = trans.CreateNPCPassenger(guid, data);
_creature.SaveToDB((uint)trans.GetGoInfo().MoTransport.SpawnMap, new List<Difficulty>() { map.GetDifficultyID() });
@@ -607,7 +680,7 @@ namespace Game.Chat
// To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells()
// current "creature" variable is deleted and created fresh new, otherwise old values might trigger asserts or cause undefined behavior
creature.CleanupsBeforeDelete();
creature = Creature.CreateCreatureFromDB(db_guid, map);
creature = Creature.CreateCreatureFromDB(db_guid, map, true, true);
if (!creature)
return false;
@@ -794,7 +867,7 @@ namespace Game.Chat
[Command("delete", RBACPermissions.CommandNpcDelete)]
static bool HandleNpcDeleteCommand(StringArguments args, CommandHandler handler)
{
Creature unit = null;
Creature creature;
if (!args.Empty())
{
@@ -806,21 +879,27 @@ namespace Game.Chat
if (!ulong.TryParse(cId, out ulong guidLow) || guidLow == 0)
return false;
unit = handler.GetCreatureFromPlayerMapByDbGuid(guidLow);
creature = handler.GetCreatureFromPlayerMapByDbGuid(guidLow);
}
else
unit = handler.GetSelectedCreature();
creature = handler.GetSelectedCreature();
if (!unit || unit.IsPet() || unit.IsTotem())
if (!creature || creature.IsPet() || creature.IsTotem())
{
handler.SendSysMessage(CypherStrings.SelectCreature);
return false;
}
// Delete the creature
unit.CombatStop();
unit.DeleteFromDB();
unit.AddObjectToRemoveList();
TempSummon summon = creature.ToTempSummon();
if (summon != null)
summon.UnSummon();
else
{
// Delete the creature
creature.CombatStop();
creature.DeleteFromDB();
creature.AddObjectToRemoveList();
}
handler.SendSysMessage(CypherStrings.CommandDelcreatmessage);
+4 -4
View File
@@ -559,7 +559,7 @@ namespace Game.Chat.Commands
creature.Dispose();
// To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells();
creature = Creature.CreateCreatureFromDB(dbGuid, map);
creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true);
if (!creature)
{
handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, 1);
@@ -779,7 +779,7 @@ namespace Game.Chat.Commands
creature.Dispose();
// To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells();
creature = Creature.CreateCreatureFromDB(dbGuid, map);
creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true);
if (!creature)
{
handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id);
@@ -844,7 +844,7 @@ namespace Game.Chat.Commands
creature.CleanupsBeforeDelete();
creature.Dispose();
creature = Creature.CreateCreatureFromDB(dbGuid, map);
creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true);
if (!creature)
{
handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, 1);
@@ -899,7 +899,7 @@ namespace Game.Chat.Commands
creature.CleanupsBeforeDelete();
creature.Dispose();
creature = Creature.CreateCreatureFromDB(dbGuid, map);
creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true);
if (!creature)
{
handler.SendSysMessage(CypherStrings.WaypointNotcreated, 1);
+3 -3
View File
@@ -1328,7 +1328,7 @@ namespace Game
CreatureData creatureData = Global.ObjectMgr.GetCreatureData(cond.ConditionValue3);
if (creatureData != null)
{
if (cond.ConditionValue2 != 0 && creatureData.id != cond.ConditionValue2)
if (cond.ConditionValue2 != 0 && creatureData.Id != cond.ConditionValue2)
{
Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match creature entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2);
return false;
@@ -1349,10 +1349,10 @@ namespace Game
}
if (cond.ConditionValue3 != 0)
{
GameObjectData goData = Global.ObjectMgr.GetGOData(cond.ConditionValue3);
GameObjectData goData = Global.ObjectMgr.GetGameObjectData(cond.ConditionValue3);
if (goData != null)
{
if (cond.ConditionValue2 != 0 && goData.id != cond.ConditionValue2)
if (cond.ConditionValue2 != 0 && goData.Id != cond.ConditionValue2)
{
Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match gameobject entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2);
return false;
@@ -71,6 +71,7 @@ namespace Game.Entities
//Formation var
CreatureGroup m_formation;
bool TriggerJustRespawned;
bool m_respawnCompatibilityMode;
public uint[] m_spells = new uint[SharedConst.MaxCreatureSpells];
+247 -158
View File
@@ -123,45 +123,62 @@ namespace Game.Entities
if (GetDeathState() != DeathState.Corpse)
return;
m_corpseRemoveTime = Time.UnixTime;
SetDeathState(DeathState.Dead);
RemoveAllAuras();
DestroyForNearbyPlayers(); // old UpdateObjectVisibility()
loot.Clear();
uint respawnDelay = m_respawnDelay;
if (IsAIEnabled)
GetAI().CorpseRemoved(respawnDelay);
if (destroyForNearbyPlayers)
DestroyForNearbyPlayers();
// Should get removed later, just keep "compatibility" with scripts
if (setSpawnTime)
m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime);
// if corpse was removed during falling, the falling will continue and override relocation to respawn position
if (IsFalling())
StopMoving();
float x, y, z, o;
GetRespawnPosition(out x, out y, out z, out o);
// We were spawned on transport, calculate real position
if (IsSpawnedOnTransport())
if (m_respawnCompatibilityMode)
{
Position pos = m_movementInfo.transport.pos;
pos.posX = x;
pos.posY = y;
pos.posZ = z;
pos.SetOrientation(o);
m_corpseRemoveTime = Time.UnixTime;
SetDeathState(DeathState.Dead);
RemoveAllAuras();
//DestroyForNearbyPlayers(); // old UpdateObjectVisibility()
loot.Clear();
uint respawnDelay = m_respawnDelay;
if (IsAIEnabled)
GetAI().CorpseRemoved(respawnDelay);
ITransport transport = GetDirectTransport();
if (transport != null)
transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o);
if (destroyForNearbyPlayers)
DestroyForNearbyPlayers();
// Should get removed later, just keep "compatibility" with scripts
if (setSpawnTime)
m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime);
// if corpse was removed during falling, the falling will continue and override relocation to respawn position
if (IsFalling())
StopMoving();
float x, y, z, o;
GetRespawnPosition(out x, out y, out z, out o);
// We were spawned on transport, calculate real position
if (IsSpawnedOnTransport())
{
Position pos = m_movementInfo.transport.pos;
pos.posX = x;
pos.posY = y;
pos.posZ = z;
pos.SetOrientation(o);
ITransport transport = GetDirectTransport();
if (transport != null)
transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o);
}
SetHomePosition(x, y, z, o);
GetMap().CreatureRelocation(this, x, y, z, o);
}
else
{
// In case this is called directly and normal respawn timer not set
// Since this timer will be longer than the already present time it
// will be ignored if the correct place added a respawn timer
if (setSpawnTime)
{
uint respawnDelay = m_respawnDelay;
m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime);
SetHomePosition(x, y, z, o);
GetMap().CreatureRelocation(this, x, y, z, o);
SaveRespawnTime(0, false);
}
AddObjectToRemoveList();
}
}
public bool InitEntry(uint entry, CreatureData data = null)
@@ -375,7 +392,7 @@ namespace Game.Entities
{
case DeathState.JustRespawned:
case DeathState.JustDied:
Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {2}", GetGUID().ToString(), m_deathState);
Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {1}", GetGUID().ToString(), m_deathState);
break;
case DeathState.Dead:
{
@@ -383,22 +400,25 @@ namespace Game.Entities
if (m_respawnTime <= now)
{
// First check if there are any scripts that object to us respawning
if (!Global.ScriptMgr.CanSpawn(GetSpawnId(), GetEntry(), GetCreatureTemplate(), GetCreatureData(), GetMap()))
break; // Will be rechecked on next Update call
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
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.Day);
SetRespawnTime(Time.Week);
else
{
// else copy time from master and add a little
long baseRespawnTime = Math.Max(linkedRespawntime, now);
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
@@ -731,7 +751,7 @@ namespace Game.Entities
lowGuid = map.GenerateLowGuid(HighGuid.Creature);
Creature creature = new Creature();
if (!creature.Create(lowGuid, map, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), null, vehId))
if (!creature.Create(lowGuid, map, entry, pos, null, vehId))
return null;
return creature;
@@ -740,13 +760,13 @@ namespace Game.Entities
public static Creature CreateCreatureFromDB(ulong spawnId, Map map, bool addToMap = true, bool allowDuplicate = false)
{
Creature creature = new Creature();
if (!creature.LoadCreatureFromDB(spawnId, map, addToMap, allowDuplicate))
if (!creature.LoadFromDB(spawnId, map, addToMap, allowDuplicate))
return null;
return creature;
}
public bool Create(ulong guidlow, Map map, uint entry, float x, float y, float z, float ang, CreatureData data, uint vehId)
public bool Create(ulong guidlow, Map map, uint entry, Position pos, CreatureData data = null, uint vehId = 0, bool dynamic = false)
{
SetMap(map);
@@ -756,6 +776,10 @@ namespace Game.Entities
PhasingHandler.InitDbVisibleMapId(GetPhaseShift(), data.terrainSwapMap);
}
// Set if this creature can handle dynamic spawns
if (!dynamic)
SetRespawnCompatibilityMode();
CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry);
if (cinfo == null)
{
@@ -765,13 +789,13 @@ namespace Game.Entities
//! Relocate before CreateFromProto, to initialize coords and allow
//! returning correct zone id for selecting OutdoorPvP/Battlefield script
Relocate(x, y, z, ang);
Relocate(pos);
// Check if the position is valid before calling CreateFromProto(), otherwise we might add Auras to Creatures at
// invalid position, triggering a crash about Auras not removed in the destructor
if (!IsPositionValid())
{
Log.outError(LogFilter.Unit, "Creature.Create: given coordinates for creature (guidlow {0}, entry {1}) are not valid (X: {2}, Y: {3}, Z: {4}, O: {5})", guidlow, entry, x, y, z, ang);
Log.outError(LogFilter.Unit, $"Creature.Create: given coordinates for creature (guidlow {guidlow}, entry {entry}) are not valid ({pos})");
return false;
}
UpdatePositionData();
@@ -810,10 +834,8 @@ namespace Game.Entities
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
if (HasUnitMovementFlag(MovementFlag.Hover))
{
z += m_unitData.HoverHeight;
//! Relocate again with updated Z coord
Relocate(x, y, z, ang);
posZ += m_unitData.HoverHeight;
}
LastUsedScriptID = GetScriptId();
@@ -924,6 +946,14 @@ namespace Game.Entities
&& !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill);
}
public bool IsEscortNPC(bool onlyIfActive = true)
{
if (!IsAIEnabled)
return false;
return GetAI().IsEscortNPC(onlyIfActive);
}
public override bool IsMovementPreventedByCasting()
{
// first check if currently a movement allowed channel is active and we're not casting
@@ -1068,16 +1098,20 @@ namespace Game.Entities
dynamicflags = 0;
}
// data.guid = guid must not be updated at save
data.id = GetEntry();
data.mapid = (ushort)mapid;
if (data.spawnId == 0)
data.spawnId = m_spawnId;
Cypher.Assert(data.spawnId == m_spawnId);
data.Id = GetEntry();
data.displayid = displayId;
data.equipmentId = GetCurrentEquipmentId();
data.posX = GetPositionX();
data.posY = GetPositionY();
data.posZ = GetPositionZMinusOffset();
data.orientation = GetOrientation();
data.spawntimesecs = m_respawnDelay;
data.equipmentId = (sbyte)GetCurrentEquipmentId();
if (GetTransport() == null)
data.spawnPoint.WorldRelocate(this);
else
data.spawnPoint.WorldRelocate(mapid, GetTransOffsetX(), GetTransOffsetY(), GetTransOffsetZ(), GetTransOffsetO());
data.spawntimesecs = (int)m_respawnDelay;
// prevent add data integrity problems
data.spawndist = GetDefaultMovementType() == MovementGeneratorType.Idle ? 0.0f : m_respawnradius;
data.currentwaypoint = 0;
@@ -1092,6 +1126,8 @@ namespace Game.Entities
data.unit_flags2 = unitFlags2;
data.unit_flags3 = unitFlags3;
data.dynamicflags = (uint)dynamicflags;
if (data.spawnGroupData == null)
data.spawnGroupData = Global.ObjectMgr.GetDefaultSpawnGroup();
data.phaseId = GetDBPhase() > 0 ? (uint)GetDBPhase() : data.phaseId;
data.phaseGroup = GetDBPhase() < 0 ? (uint)-GetDBPhase() : data.phaseGroup;
@@ -1414,7 +1450,7 @@ namespace Game.Entities
return;
}
GetMap().RemoveCreatureRespawnTime(m_spawnId);
GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId);
Global.ObjectMgr.DeleteCreatureData(m_spawnId);
SQLTransaction trans = new SQLTransaction();
@@ -1423,6 +1459,11 @@ namespace Game.Entities
stmt.AddValue(0, m_spawnId);
trans.Append(stmt);
stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_SPAWNGROUP_MEMBER);
stmt.AddValue(0, (byte)SpawnObjectType.Creature);
stmt.AddValue(1, m_spawnId);
trans.Append(stmt);
stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE_ADDON);
stmt.AddValue(0, m_spawnId);
trans.Append(stmt);
@@ -1549,14 +1590,31 @@ namespace Game.Entities
if (s == DeathState.JustDied)
{
m_corpseRemoveTime = Time.UnixTime + m_corpseDelay;
if (IsDungeonBoss() && m_respawnDelay == 0)
m_respawnTime = long.MaxValue; // never respawn in this instance
uint respawnDelay = m_respawnDelay;
uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode);
if (scalingMode != 0)
GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode);
// @todo remove the boss respawn time hack in a dynspawn follow-up once we have creature groups in instances
if (m_respawnCompatibilityMode)
{
if (IsDungeonBoss() && m_respawnDelay == 0)
m_respawnTime = long.MaxValue; // never respawn in this instance
else
m_respawnTime = Time.UnixTime + respawnDelay + m_corpseDelay;
}
else
m_respawnTime = Time.UnixTime + m_respawnDelay + m_corpseDelay;
{
if (IsDungeonBoss() && m_respawnDelay == 0)
m_respawnTime = long.MaxValue; // never respawn in this instance
else
m_respawnTime = Time.UnixTime + respawnDelay;
}
// always save boss respawn time at death to prevent crash cheating
if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately) || IsWorldBoss())
SaveRespawnTime();
else if (!m_respawnCompatibilityMode)
SaveRespawnTime(0, false);
ReleaseFocus(null, false); // remove spellcast focus
DoNotReacquireTarget(); // cancel delayed re-target
@@ -1632,8 +1690,6 @@ namespace Game.Entities
public void Respawn(bool force = false)
{
DestroyForNearbyPlayers();
if (force)
{
if (IsAlive())
@@ -1642,49 +1698,60 @@ namespace Game.Entities
SetDeathState(DeathState.Corpse);
}
RemoveCorpse(false, false);
if (GetDeathState() == DeathState.Dead)
if (m_respawnCompatibilityMode)
{
if (m_spawnId != 0)
GetMap().RemoveCreatureRespawnTime(m_spawnId);
DestroyForNearbyPlayers();
RemoveCorpse(false, false);
Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString());
m_respawnTime = 0;
ResetPickPocketRefillTimer();
loot.Clear();
if (GetDeathState() == DeathState.Dead)
{
if (m_spawnId != 0)
GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId);
Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString());
m_respawnTime = 0;
ResetPickPocketRefillTimer();
loot.Clear();
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
else
SelectLevel();
SetDeathState(DeathState.JustRespawned);
SetDeathState(DeathState.JustRespawned);
CreatureModel display = new CreatureModel(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f);
if (Global.ObjectMgr.GetCreatureModelRandomGender(ref display, GetCreatureTemplate()) != null)
{
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale);
CreatureModel display = new CreatureModel(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f);
if (Global.ObjectMgr.GetCreatureModelRandomGender(ref display, GetCreatureTemplate()) != null)
{
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale);
}
GetMotionMaster().InitDefault();
//Re-initialize reactstate that could be altered by movementgenerators
InitializeReactState();
//Call AI respawn virtual function//Call AI respawn virtual function
if (IsAIEnabled)
{
//reset the AI to be sure no dirty or uninitialized values will be used till next tick
GetAI().Reset();
TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing
}
uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool<Creature>(GetSpawnId()) : 0;
if (poolid != 0)
Global.PoolMgr.UpdatePool<Creature>(poolid, GetSpawnId());
}
GetMotionMaster().InitDefault();
//Re-initialize reactstate that could be altered by movementgenerators
InitializeReactState();
//Call AI respawn virtual function
if (IsAIEnabled)
{
GetAI().Reset();
TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing
}
uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool<Creature>(GetSpawnId()) : 0;
if (poolid != 0)
Global.PoolMgr.UpdatePool<Creature>(poolid, GetSpawnId());
UpdateObjectVisibility();
}
else
{
if (m_spawnId != 0)
GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId, true);
}
UpdateObjectVisibility();
Log.outDebug(LogFilter.Unit, $"Respawning creature {GetName()} ({GetGUID()})");
}
public void ForcedDespawn(uint timeMSToDespawn = 0, TimeSpan forceRespawnTimer = default)
@@ -1695,30 +1762,49 @@ namespace Game.Entities
return;
}
uint corpseDelay = GetCorpseDelay();
uint respawnDelay = GetRespawnDelay();
// do it before killing creature
DestroyForNearbyPlayers();
bool overrideRespawnTime = false;
if (IsAlive())
if (m_respawnCompatibilityMode)
{
if (forceRespawnTimer > TimeSpan.Zero)
uint corpseDelay = GetCorpseDelay();
uint respawnDelay = GetRespawnDelay();
// do it before killing creature
DestroyForNearbyPlayers();
bool overrideRespawnTime = false;
if (IsAlive())
{
SetCorpseDelay(0);
SetRespawnDelay((uint)forceRespawnTimer.TotalSeconds);
overrideRespawnTime = false;
if (forceRespawnTimer > TimeSpan.Zero)
{
SetCorpseDelay(0);
SetRespawnDelay((uint)forceRespawnTimer.TotalSeconds);
overrideRespawnTime = false;
}
SetDeathState(DeathState.JustDied);
}
SetDeathState(DeathState.JustDied);
// Skip corpse decay time
RemoveCorpse(overrideRespawnTime, false);
SetCorpseDelay(corpseDelay);
SetRespawnDelay(respawnDelay);
}
else
{
if (forceRespawnTimer > TimeSpan.Zero)
SaveRespawnTime((uint)forceRespawnTimer.TotalSeconds);
else
{
uint respawnDelay = m_respawnDelay;
uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode);
if (scalingMode != 0)
GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode);
m_respawnTime = Time.UnixTime + respawnDelay;
SaveRespawnTime();
}
// Skip corpse decay time
RemoveCorpse(overrideRespawnTime, false);
SetCorpseDelay(corpseDelay);
SetRespawnDelay(respawnDelay);
AddObjectToRemoveList();
}
}
public void DespawnOrUnsummon(TimeSpan time, TimeSpan forceRespawnTimer = default) { DespawnOrUnsummon((uint)time.TotalMilliseconds, forceRespawnTimer); }
@@ -2074,12 +2160,19 @@ namespace Game.Entities
return false;
}
public override void SaveRespawnTime()
public override void SaveRespawnTime(uint forceDelay = 0, bool saveToDb = true)
{
if (IsSummon() || m_spawnId == 0 || (m_creatureData != null && !m_creatureData.dbData))
return;
GetMap().SaveCreatureRespawnTime(m_spawnId, m_respawnTime);
if (m_respawnCompatibilityMode)
{
GetMap().SaveRespawnTimeDB(SpawnObjectType.Creature, m_spawnId, m_respawnTime);
return;
}
uint thisRespawnTime = (uint)(forceDelay != 0 ? Time.UnixTime + forceDelay : m_respawnTime);
GetMap().SaveRespawnTime(SpawnObjectType.Creature, m_spawnId, GetEntry(), thisRespawnTime, GetMap().GetZoneId(GetPhaseShift(), GetHomePosition()), GridDefines.ComputeGridCoord(GetHomePosition().GetPositionX(), GetHomePosition().GetPositionY()).GetId(), m_creatureData.dbData && saveToDb);
}
public bool CanCreatureAttack(Unit victim, bool force = true)
@@ -2293,32 +2386,20 @@ namespace Game.Entities
}
public void GetRespawnPosition(out float x, out float y, out float z, out float ori, out float dist)
{
// for npcs on transport, this will return transport offset
if (m_spawnId != 0)
if (m_creatureData != null)
{
CreatureData data = Global.ObjectMgr.GetCreatureData(GetSpawnId());
if (data != null)
{
x = data.posX;
y = data.posY;
z = data.posZ;
ori = data.orientation;
dist = data.spawndist;
return;
}
m_creatureData.spawnPoint.GetPosition(out x, out y, out z, out ori);
dist = m_creatureData.spawndist;
}
else
{
Position homePos = GetHomePosition();
homePos.GetPosition(out x, out y, out z, out ori);
dist = 0;
}
// changed this from current position to home position, fixes world summons with infinite duration (wg npcs for example)
Position homePos = GetHomePosition();
x = homePos.GetPositionX();
y = homePos.GetPositionY();
z = homePos.GetPositionZ();
ori = homePos.GetOrientation();
dist = 0;
}
bool IsSpawnedOnTransport() { return m_creatureData != null && m_creatureData.mapid != GetMapId(); }
bool IsSpawnedOnTransport() { return m_creatureData != null && m_creatureData.spawnPoint.GetMapId() != GetMapId(); }
public void AllLootRemovedFromCorpse()
{
@@ -2934,12 +3015,7 @@ namespace Game.Entities
public CreatureTemplate GetCreatureTemplate() { return m_creatureInfo; }
public CreatureData GetCreatureData() { return m_creatureData; }
public override bool LoadFromDB(ulong spawnId, Map map)
{
return LoadCreatureFromDB(spawnId, map, false, false);
}
public bool LoadCreatureFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate)
public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate)
{
if (!allowDuplicate)
{
@@ -2976,39 +3052,48 @@ namespace Game.Entities
}
m_spawnId = spawnId;
m_respawnCompatibilityMode = data.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.CompatibilityMode);
m_creatureData = data;
m_respawnradius = data.spawndist;
m_respawnDelay = data.spawntimesecs;
if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.id, data.posX, data.posY, data.posZ, data.orientation, data, 0))
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))
return false;
//We should set first home position, because then AI calls home movement
SetHomePosition(data.posX, data.posY, data.posZ, data.orientation);
SetHomePosition(data.spawnPoint);
m_deathState = DeathState.Alive;
m_respawnTime = GetMap().GetCreatureRespawnTime(m_spawnId);
// Is the creature script objecting to us spawning? If yes, delay by one second (then re-check in ::Update)
if (m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, GetEntry(), GetCreatureTemplate(), GetCreatureData(), map))
m_respawnTime = Time.UnixTime + 1;
// 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))
m_respawnTime = Time.UnixTime + RandomHelper.URand(4, 7);
if (m_respawnTime != 0) // respawn on Update
if (m_respawnTime != 0) // respawn on UpdateLoadCreatureFromDB
{
m_deathState = DeathState.Dead;
if (CanFly())
{
float tz = map.GetHeight(GetPhaseShift(), data.posX, data.posY, data.posZ, true, MapConst.MaxFallDistance);
if (data.posZ - tz > 0.1f && GridDefines.IsValidMapCoord(tz))
Relocate(data.posX, data.posY, tz);
float tz = map.GetHeight(GetPhaseShift(), data.spawnPoint, true, MapConst.MaxFallDistance);
if (data.spawnPoint.GetPositionZ() - tz > 0.1f && GridDefines.IsValidMapCoord(tz))
Relocate(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY(), tz);
}
}
SetSpawnHealth();
// checked at creature_template loading
DefaultMovementType = (MovementGeneratorType)data.movementType;
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, data.mapid, data.id, GetMap().GenerateLowGuid(HighGuid.LootObject)));
loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, GetMapId(), data.Id, GetMap().GenerateLowGuid(HighGuid.LootObject)));
if (addToMap && !GetMap().AddToMap(this))
return false;
@@ -3102,6 +3187,10 @@ namespace Game.Entities
m_originalEntry = entry;
}
// There's many places not ready for dynamic spawns. This allows them to live on for now.
void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; }
public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; }
public Unit SelectVictim()
{
// function provides main threat functionality
+8 -19
View File
@@ -21,6 +21,7 @@ using System;
using System.Collections.Generic;
using Framework.Dynamic;
using Game.Networking.Packets;
using Game.Maps;
namespace Game.Entities
{
@@ -327,34 +328,22 @@ namespace Game.Entities
public EquipmentItem[] Items = new EquipmentItem[SharedConst.MaxEquipmentItems];
}
public class CreatureData
public class CreatureData : SpawnData
{
public uint id; // entry in creature_template
public ushort mapid;
public uint displayid;
public int equipmentId;
public float posX;
public float posY;
public float posZ;
public float orientation;
public uint spawntimesecs;
public sbyte equipmentId;
public float spawndist;
public uint currentwaypoint;
public uint curhealth;
public uint curmana;
public byte movementType;
public List<Difficulty> spawnDifficulties = new List<Difficulty>();
public ulong npcflag;
public uint unit_flags; // enum UnitFlags mask values
public uint unit_flags2; // enum UnitFlags2 mask values
public uint unit_flags3; // enum UnitFlags3 mask values
public uint unit_flags; // enum UnitFlags mask values
public uint unit_flags2; // enum UnitFlags2 mask values
public uint unit_flags3; // enum UnitFlags3 mask values
public uint dynamicflags;
public PhaseUseFlagsValues phaseUseFlags;
public uint phaseId;
public uint phaseGroup;
public int terrainSwapMap;
public uint ScriptId;
public bool dbData;
public CreatureData() : base(SpawnObjectType.Creature) { }
}
public class CreatureModelInfo
+151 -123
View File
@@ -166,7 +166,7 @@ namespace Game.Entities
return null;
GameObject go = new GameObject();
if (!go.Create(entry, map, pos, rotation, animProgress, goState, artKit))
if (!go.Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0))
return null;
return go;
@@ -175,13 +175,13 @@ namespace Game.Entities
public static GameObject CreateGameObjectFromDB(ulong spawnId, Map map, bool addToMap = true)
{
GameObject go = new GameObject();
if (!go.LoadGameObjectFromDB(spawnId, map, addToMap))
if (!go.LoadFromDB(spawnId, map, addToMap))
return null;
return go;
}
bool Create(uint entry, Map map, Position pos, Quaternion rotation, uint animProgress, GameObjectState goState, uint artKit)
bool Create(uint entry, Map map, Position pos, Quaternion rotation, uint animProgress, GameObjectState goState, uint artKit, bool dynamic, ulong spawnid)
{
Cypher.Assert(map);
SetMap(map);
@@ -194,6 +194,10 @@ namespace Game.Entities
return false;
}
// Set if this object can handle dynamic spawns
if (!dynamic)
SetRespawnCompatibilityMode();
UpdatePositionData();
SetZoneScript();
@@ -373,6 +377,9 @@ namespace Game.Entities
if (map.Is25ManRaid())
loot.maxDuplicates = 3;
if (spawnid != 0)
m_spawnId = spawnid;
uint linkedEntry = GetGoInfo().GetLinkedGameObjectEntry();
if (linkedEntry != 0)
{
@@ -507,81 +514,88 @@ namespace Game.Entities
goto case LootState.Ready;
case LootState.Ready:
{
if (m_respawnTime > 0) // timer on
if (m_respawnCompatibilityMode)
{
long now = Time.UnixTime;
if (m_respawnTime <= now) // timer expired
if (m_respawnTime > 0) // timer on
{
ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.GameObject, GetMapId(), GetEntry(), m_spawnId);
long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid);
if (linkedRespawntime != 0) // Can't respawn, the master is dead
long now = Time.UnixTime;
if (m_respawnTime <= now) // timer expired
{
ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid);
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
SetRespawnTime(Time.Day);
else
m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little
SaveRespawnTime(); // also save to DB immediately
return;
}
ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.GameObject, GetMapId(), GetEntry(), m_spawnId);
long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid);
if (linkedRespawntime != 0) // Can't respawn, 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
m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little
SaveRespawnTime(); // also save to DB immediately
return;
}
m_respawnTime = 0;
m_SkillupList.Clear();
m_usetimes = 0;
m_respawnTime = 0;
m_SkillupList.Clear();
m_usetimes = 0;
// If nearby linked trap exists, respawn it
GameObject linkedTrap = GetLinkedTrap();
if (linkedTrap)
linkedTrap.SetLootState(LootState.Ready);
// If nearby linked trap exists, respawn it
GameObject linkedTrap = GetLinkedTrap();
if (linkedTrap)
linkedTrap.SetLootState(LootState.Ready);
switch (GetGoType())
{
case GameObjectTypes.FishingNode: // can't fish now
{
Unit caster = GetOwner();
if (caster != null && caster.IsTypeId(TypeId.Player))
switch (GetGoType())
{
case GameObjectTypes.FishingNode: // can't fish now
{
caster.ToPlayer().RemoveGameObject(this, false);
caster.ToPlayer().SendPacket(new FishEscaped());
Unit caster = GetOwner();
if (caster != null && caster.IsTypeId(TypeId.Player))
{
caster.ToPlayer().RemoveGameObject(this, false);
caster.ToPlayer().SendPacket(new FishEscaped());
}
// can be delete
m_lootState = LootState.JustDeactivated;
return;
}
// can be delete
m_lootState = LootState.JustDeactivated;
return;
}
case GameObjectTypes.Door:
case GameObjectTypes.Button:
//we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for Battlegrounds)
if (GetGoState() != GameObjectState.Ready)
ResetDoorOrButton();
break;
case GameObjectTypes.FishingHole:
// Initialize a new max fish count on respawn
m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock);
break;
default:
break;
case GameObjectTypes.Door:
case GameObjectTypes.Button:
//we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for Battlegrounds)
if (GetGoState() != GameObjectState.Ready)
ResetDoorOrButton();
break;
case GameObjectTypes.FishingHole:
// Initialize a new max fish count on respawn
m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock);
break;
default:
break;
}
if (!m_spawnedByDefault) // despawn timer
{
// can be despawned or destroyed
SetLootState(LootState.JustDeactivated);
return;
}
// Call AI Reset (required for example in SmartAI to clear one time events)
if (GetAI() != null)
GetAI().Reset();
// respawn timer
uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool<GameObject>(GetSpawnId()) : 0;
if (poolid != 0)
Global.PoolMgr.UpdatePool<GameObject>(poolid, GetSpawnId());
else
GetMap().AddToMap(this);
}
if (!m_spawnedByDefault) // despawn timer
{
// can be despawned or destroyed
SetLootState(LootState.JustDeactivated);
return;
}
// Call AI Reset (required for example in SmartAI to clear one time events)
if (GetAI() != null)
GetAI().Reset();
// respawn timer
uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool<GameObject>(GetSpawnId()) : 0;
if (poolid != 0)
Global.PoolMgr.UpdatePool<GameObject>(poolid, GetSpawnId());
else
GetMap().AddToMap(this);
}
}
// Set respawn timer
if (!m_respawnCompatibilityMode && m_respawnTime > 0)
SaveRespawnTime(0, false);
if (IsSpawned())
{
GameObjectTemplate goInfo = GetGoInfo();
@@ -785,6 +799,7 @@ namespace Game.Entities
if (m_respawnDelayTime == 0)
return;
// ToDo: Decide if we should properly despawn these. Maybe they expect to be able to manually respawn from script?
if (!m_spawnedByDefault)
{
m_respawnTime = 0;
@@ -792,12 +807,29 @@ namespace Game.Entities
return;
}
m_respawnTime = Time.UnixTime + m_respawnDelayTime;
uint respawnDelay = m_respawnDelayTime;
uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode);
if (scalingMode != 0)
GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode);
m_respawnTime = Time.UnixTime + respawnDelay;
// if option not set then object will be saved at grid unload
// Otherwise just save respawn time to map object memory
if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately))
SaveRespawnTime();
if (!m_respawnCompatibilityMode)
{
// Respawn time was just saved if set to save to DB
// If not, we save only to map memory
if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately))
SaveRespawnTime(0, false);
// Then despawn
AddObjectToRemoveList();
return;
}
DestroyForNearbyPlayers(); // old UpdateObjectVisibility()
break;
}
@@ -890,7 +922,7 @@ namespace Game.Entities
{
// this should only be used when the gameobject has already been loaded
// preferably after adding to map, because mapid may not be valid otherwise
GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(m_spawnId);
if (data == null)
{
Log.outError(LogFilter.Maps, "GameObject.SaveToDB failed, cannot get gameobject data!");
@@ -911,22 +943,22 @@ namespace Game.Entities
m_spawnId = Global.ObjectMgr.GenerateGameObjectSpawnId();
// update in loaded data (changing data only in this place)
GameObjectData data = new GameObjectData();
GameObjectData data = Global.ObjectMgr.NewOrExistGameObjectData(m_spawnId);
// guid = guid must not be updated at save
data.id = GetEntry();
data.mapid = (ushort)mapid;
data.posX = GetPositionX();
data.posY = GetPositionY();
data.posZ = GetPositionZ();
data.orientation = GetOrientation();
if (data.spawnId == 0)
data.spawnId = m_spawnId;
Cypher.Assert(data.spawnId == m_spawnId);
data.Id = GetEntry();
data.spawnPoint.WorldRelocate(this);
data.rotation = m_worldRotation;
data.spawntimesecs = (int)(m_spawnedByDefault ? m_respawnDelayTime : -m_respawnDelayTime);
data.animprogress = GetGoAnimProgress();
data.go_state = GetGoState();
data.goState = GetGoState();
data.spawnDifficulties = spawnDifficulties;
data.artKit = (byte)GetGoArtKit();
Global.ObjectMgr.NewGOData(m_spawnId, data);
if (data.spawnGroupData == null)
data.spawnGroupData = Global.ObjectMgr.GetDefaultSpawnGroup();
data.phaseId = GetDBPhase() > 0 ? (uint)GetDBPhase() : data.phaseId;
data.phaseGroup = GetDBPhase() < 0 ? (uint)-GetDBPhase() : data.phaseGroup;
@@ -958,24 +990,24 @@ namespace Game.Entities
DB.World.Execute(stmt);
}
bool LoadGameObjectFromDB(ulong spawnId, Map map, bool addToMap)
public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool unused = true)
{
GameObjectData data = Global.ObjectMgr.GetGOData(spawnId);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(spawnId);
if (data == null)
{
Log.outError(LogFilter.Maps, "Gameobject (SpawnId: {0}) not found in table `gameobject`, can't load. ", spawnId);
return false;
}
uint entry = data.id;
Position pos = new Position(data.posX, data.posY, data.posZ, data.orientation);
uint entry = data.Id;
uint animprogress = data.animprogress;
GameObjectState go_state = data.go_state;
GameObjectState go_state = data.goState;
uint artKit = data.artKit;
m_spawnId = spawnId;
if (!Create(entry, map, pos, data.rotation, animprogress, go_state, artKit))
m_respawnCompatibilityMode = ((data.spawnGroupData.flags & SpawnGroupFlags.CompatibilityMode) != 0);
if (!Create(entry, map, data.spawnPoint, data.rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId))
return false;
PhasingHandler.InitDbPhaseShift(GetPhaseShift(), data.phaseUseFlags, data.phaseId, data.phaseGroup);
@@ -1000,7 +1032,7 @@ namespace Game.Entities
if (m_respawnTime != 0 && m_respawnTime <= Time.UnixTime)
{
m_respawnTime = 0;
GetMap().RemoveGORespawnTime(m_spawnId);
GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId);
}
}
}
@@ -1021,20 +1053,20 @@ namespace Game.Entities
public void DeleteFromDB()
{
GetMap().RemoveGORespawnTime(m_spawnId);
Global.ObjectMgr.DeleteGOData(m_spawnId);
GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId);
Global.ObjectMgr.DeleteGameObjectData(m_spawnId);
SQLTransaction trans = new SQLTransaction();
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAMEOBJECT);
stmt.AddValue(0, m_spawnId);
DB.World.Execute(stmt);
trans.Append(stmt);
stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_EVENT_GAMEOBJECT);
stmt.AddValue(0, m_spawnId);
trans.Append(stmt);
DB.World.Execute(stmt);
DB.World.CommitTransaction(trans);
}
public override bool HasQuest(uint quest_id)
@@ -1097,10 +1129,19 @@ namespace Game.Entities
return Global.ObjAccessor.GetUnit(this, GetOwnerGUID());
}
public override void SaveRespawnTime()
public override void SaveRespawnTime(uint forceDelay = 0, bool savetodb = true)
{
if (m_goData != null && m_goData.dbData && m_respawnTime > Time.UnixTime && m_spawnedByDefault)
GetMap().SaveGORespawnTime(m_spawnId, m_respawnTime);
if (m_goData != null && m_respawnTime > Time.UnixTime && m_spawnedByDefault)
{
if (m_respawnCompatibilityMode)
{
GetMap().SaveRespawnTimeDB(SpawnObjectType.GameObject, m_spawnId, m_respawnTime);
return;
}
uint thisRespawnTime = (uint)(forceDelay != 0 ? Time.UnixTime + forceDelay : m_respawnTime);
GetMap().SaveRespawnTime(SpawnObjectType.GameObject, m_spawnId, GetEntry(), thisRespawnTime, GetZoneId(), GridDefines.ComputeGridCoord(GetPositionX(), GetPositionY()).GetId(), m_goData.dbData ? savetodb : false);
}
}
public override bool IsNeverVisibleFor(WorldObject seer)
@@ -1160,7 +1201,7 @@ namespace Game.Entities
if (m_spawnedByDefault && m_respawnTime > 0)
{
m_respawnTime = Time.UnixTime;
GetMap().RemoveGORespawnTime(m_spawnId);
GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId, true);
}
}
@@ -1262,7 +1303,7 @@ namespace Game.Entities
public void SetGoArtKit(byte kit)
{
SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.ArtKit), kit);
GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(m_spawnId);
if (data != null)
data.artKit = kit;
}
@@ -1273,10 +1314,10 @@ namespace Game.Entities
if (go != null)
{
go.SetGoArtKit(artkit);
data = go.GetGoData();
data = go.GetGameObjectData();
}
else if (lowguid != 0)
data = Global.ObjectMgr.GetGOData(lowguid);
data = Global.ObjectMgr.GetGameObjectData(lowguid);
if (data != null)
data.artKit = artkit;
@@ -2075,7 +2116,7 @@ namespace Game.Entities
public uint GetScriptId()
{
GameObjectData gameObjectData = GetGoData();
GameObjectData gameObjectData = GetGameObjectData();
if (gameObjectData != null)
{
uint scriptId = gameObjectData.ScriptId;
@@ -2548,23 +2589,10 @@ namespace Game.Entities
public void GetRespawnPosition(out float x, out float y, out float z, out float ori)
{
if (m_spawnId != 0)
{
GameObjectData data = Global.ObjectMgr.GetGOData(GetSpawnId());
if (data != null)
{
x = posX;
y = posY;
z = posZ;
ori = data.orientation;
return;
}
}
x = GetPositionX();
y = GetPositionY();
z = GetPositionZ();
ori = GetOrientation();
if (m_goData != null)
m_goData.spawnPoint.GetPosition(out x, out y, out z, out ori);
else
GetPosition(out x, out y, out z, out ori);
}
public float GetInteractionDistance()
@@ -2624,18 +2652,13 @@ namespace Game.Entities
public GameObjectTemplate GetGoInfo() { return m_goInfo; }
public GameObjectTemplateAddon GetTemplateAddon() { return m_goTemplateAddon; }
public GameObjectData GetGoData() { return m_goData; }
public GameObjectData GetGameObjectData() { return m_goData; }
public GameObjectValue GetGoValue() { return m_goValue; }
public ulong GetSpawnId() { return m_spawnId; }
public long GetPackedWorldRotation() { return m_packedRotation; }
public override bool LoadFromDB(ulong spawnId, Map map)
{
return LoadGameObjectFromDB(spawnId, map, false);
}
public void SetOwnerGUID(ObjectGuid owner)
{
// Owner already found and different than expected owner - remove object from old owner
@@ -2768,6 +2791,10 @@ namespace Game.Entities
AddFlag(GameObjectFlags.MapObject);
}
// There's many places not ready for dynamic spawns. This allows them to live on for now.
void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; }
public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; }
#region Fields
protected GameObjectFieldData m_gameObjectData;
protected GameObjectValue m_goValue;
@@ -2799,6 +2826,7 @@ namespace Game.Entities
public Position StationaryPosition { get; set; }
GameObjectAI m_AI;
bool m_respawnCompatibilityMode;
ushort _animKitId;
uint _worldEffectID;
@@ -21,6 +21,7 @@ using Framework.GameMath;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Game.Networking.Packets;
using Game.Maps;
namespace Game.Entities
{
@@ -1274,25 +1275,13 @@ namespace Game.Entities
public uint WorldEffectID;
}
public class GameObjectData
public class GameObjectData : SpawnData
{
public uint id; // entry in gamobject_template
public ushort mapid;
public float posX;
public float posY;
public float posZ;
public float orientation;
public Quaternion rotation;
public int spawntimesecs;
public uint animprogress;
public GameObjectState go_state;
public List<Difficulty> spawnDifficulties = new List<Difficulty>();
public GameObjectState goState;
public byte artKit;
public PhaseUseFlagsValues phaseUseFlags;
public uint phaseId;
public uint phaseGroup;
public int terrainSwapMap;
public uint ScriptId;
public bool dbData = true;
public GameObjectData() : base(SpawnObjectType.GameObject) { }
}
}
+12
View File
@@ -345,6 +345,10 @@ namespace Game.Entities
{
if (_nextGuid >= ObjectGuid.GetMaxCounter(_highGuid) - 1)
HandleCounterOverflow();
if (_highGuid == HighGuid.Creature || _highGuid == HighGuid.Vehicle || _highGuid == HighGuid.GameObject || _highGuid == HighGuid.Transport)
CheckGuidTrigger(_nextGuid);
return _nextGuid++;
}
@@ -355,5 +359,13 @@ namespace Game.Entities
Log.outFatal(LogFilter.Server, "{0} guid overflow!! Can't continue, shutting down server. ", _highGuid);
Global.WorldMgr.StopNow();
}
void CheckGuidTrigger(ulong guidlow)
{
if (!Global.WorldMgr.IsGuidAlert() && guidlow > WorldConfig.GetUInt64Value(WorldCfg.RespawnGuidAlertLevel))
Global.WorldMgr.TriggerGuidAlert();
else if (!Global.WorldMgr.IsGuidWarning() && guidlow > WorldConfig.GetUInt64Value(WorldCfg.RespawnGuidWarnLevel))
Global.WorldMgr.TriggerGuidWarning();
}
}
}
+6
View File
@@ -364,6 +364,12 @@ namespace Game.Entities
Relocate(pos);
}
public void WorldRelocate(uint mapId, Position pos)
{
_mapId = mapId;
Relocate(pos);
}
public void WorldRelocate(WorldLocation loc)
{
_mapId = loc._mapId;
+2 -2
View File
@@ -1731,7 +1731,7 @@ namespace Game.Entities
public virtual uint GetLevelForTarget(WorldObject target) { return 1; }
public virtual void SaveRespawnTime() { }
public virtual void SaveRespawnTime(uint forceDelay = 0, bool saveToDB = true) { }
public ZoneScript GetZoneScript() { return m_zoneScript; }
@@ -1770,7 +1770,7 @@ namespace Game.Entities
public virtual bool IsInvisibleDueToDespawn() { return false; }
public virtual bool IsAlwaysDetectableFor(WorldObject seer) { return false; }
public virtual bool LoadFromDB(ulong guid, Map map) { return true; }
public virtual bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { return true; }
//Position
@@ -160,6 +160,8 @@ namespace Game.Entities
guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone);
}
GetMap().UpdatePlayerZoneStats(m_zoneUpdateId, newZone);
// group update
if (GetGroup())
{
+7 -11
View File
@@ -318,14 +318,12 @@ namespace Game.Entities
{
Map map = GetMap();
Creature creature = Creature.CreateCreatureFromDB(guid, map, false);
Creature creature = Creature.CreateCreatureFromDB(guid, map, false, true);
if (!creature)
return null;
float x = data.posX;
float y = data.posY;
float z = data.posZ;
float o = data.orientation;
float x, y, z, o;
data.spawnPoint.GetPosition(out x, out y, out z, out o);
creature.SetTransport(this);
creature.m_movementInfo.transport.guid = GetGUID();
@@ -365,10 +363,8 @@ namespace Game.Entities
if (!go)
return null;
float x = data.posX;
float y = data.posY;
float z = data.posZ;
float o = data.orientation;
float x, y, z, o;
data.spawnPoint.GetPosition(out x, out y, out z, out o);
go.SetTransport(this);
go.m_movementInfo.transport.guid = GetGUID();
@@ -472,7 +468,7 @@ namespace Game.Entities
pos.GetPosition(out x, out y, out z, out o);
CalculatePassengerPosition(ref x, ref y, ref z, ref o);
if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, entry, x, y, z, o, null, vehId))
if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, entry, new Position(x, y, z, o), null, vehId))
return null;
PhasingHandler.InheritPhaseShift(summon, summoner ? (WorldObject)summoner : this);
@@ -553,7 +549,7 @@ namespace Game.Entities
// GameObjects on transport
foreach (var go in cell.Value.gameobjects)
CreateGOPassenger(go, Global.ObjectMgr.GetGOData(go));
CreateGOPassenger(go, Global.ObjectMgr.GetGameObjectData(go));
}
}
+14 -14
View File
@@ -412,7 +412,7 @@ namespace Game
int internal_event_id = mGameEvent.Length + event_id - 1;
GameObjectData data = Global.ObjectMgr.GetGOData(guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid);
if (data == null)
{
Log.outError(LogFilter.Sql, "`game_event_gameobject` contains gameobject (GUID: {0}) not found in `gameobject` table.", guid);
@@ -773,7 +773,7 @@ namespace Game
uint entry = 0;
CreatureData data = Global.ObjectMgr.GetCreatureData(guid);
if (data != null)
entry = data.id;
entry = data.Id;
VendorItem vItem = new VendorItem();
vItem.item = result.Read<uint>(2);
@@ -1103,7 +1103,7 @@ namespace Game
// get the creature data from the low guid to get the entry, to be able to find out the whole guid
CreatureData data = Global.ObjectMgr.GetCreatureData(guid);
if (data != null)
creaturesByMap.Add(data.mapid, guid);
creaturesByMap.Add(data.spawnPoint.GetMapId(), guid);
}
foreach (var key in creaturesByMap.Keys)
@@ -1170,9 +1170,9 @@ namespace Game
Global.ObjectMgr.AddCreatureToGrid(guid, data);
// Spawn if necessary (loaded grids only)
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
// We use spawn coords to spawn
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY))
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint))
Creature.CreateCreatureFromDB(guid, map);
}
}
@@ -1187,15 +1187,15 @@ namespace Game
foreach (var guid in mGameEventGameobjectGuids[internal_event_id])
{
// Add to correct cell
GameObjectData data = Global.ObjectMgr.GetGOData(guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid);
if (data != null)
{
Global.ObjectMgr.AddGameObjectToGrid(guid, data);
// Spawn if necessary (loaded grids only)
// this base map checked as non-instanced and then only existed
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
// We use current coords to unspawn, not spawn coords since creature can have changed grid
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY))
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint))
{
GameObject pGameobject = GameObject.CreateGameObjectFromDB(guid, map, false);
// @todo find out when it is add to map
@@ -1243,7 +1243,7 @@ namespace Game
{
Global.ObjectMgr.RemoveCreatureFromGrid(guid, data);
Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map =>
Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map =>
{
var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid);
foreach (var creature in creatureBounds)
@@ -1265,12 +1265,12 @@ namespace Game
if (event_id > 0 && HasGameObjectActiveEventExcept(guid, (ushort)event_id))
continue;
// Remove the gameobject from grid
GameObjectData data = Global.ObjectMgr.GetGOData(guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid);
if (data != null)
{
Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data);
Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map =>
Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map =>
{
var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid);
foreach (var go in gameobjectBounds)
@@ -1300,7 +1300,7 @@ namespace Game
continue;
// Update if spawned
Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map =>
Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map =>
{
var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(tuple.Item1);
foreach (var creature in creatureBounds)
@@ -1337,12 +1337,12 @@ namespace Game
tuple.Item2.modelid_prev = data2.displayid;
tuple.Item2.equipement_id_prev = (byte)data2.equipmentId;
data2.displayid = tuple.Item2.modelid;
data2.equipmentId = tuple.Item2.equipment_id;
data2.equipmentId = (sbyte)tuple.Item2.equipment_id;
}
else
{
data2.displayid = tuple.Item2.modelid_prev;
data2.equipmentId = tuple.Item2.equipement_id_prev;
data2.equipmentId = (sbyte)tuple.Item2.equipement_id_prev;
}
}
}
+1 -1
View File
@@ -831,7 +831,7 @@ namespace Game.Garrisons
T BuildingSpawnHelper<T>(GameObject building, ulong spawnId, Map map) where T : WorldObject, new()
{
T spawn = new T();
if (!spawn.LoadFromDB(spawnId, map))
if (!spawn.LoadFromDB(spawnId, map, false, false))
return null;
float x = spawn.GetPositionX();
File diff suppressed because it is too large Load Diff
+25
View File
@@ -2010,6 +2010,31 @@ namespace Game.Maps
bool _reqAlive;
}
class AnyPlayerInPositionRangeCheck : ICheck<Player>
{
public AnyPlayerInPositionRangeCheck(Position pos, float range, bool reqAlive = true)
{
_pos = pos;
_range = range;
_reqAlive = reqAlive;
}
public bool Invoke(Player u)
{
if (_reqAlive && !u.IsAlive())
return false;
if (!u.IsWithinDist3d(_pos, _range))
return false;
return true;
}
Position _pos;
float _range;
bool _reqAlive;
}
class NearestPlayerInObjectRangeCheck : ICheck<Player>
{
public NearestPlayerInObjectRangeCheck(WorldObject obj, float range)
+535 -80
View File
@@ -74,6 +74,8 @@ namespace Game.Maps
}
}
_zonePlayerCountMap.Clear();
//lets initialize visibility distance for map
InitVisibilityDistance();
_weatherUpdateTimer = new IntervalTimer();
@@ -90,6 +92,10 @@ namespace Game.Maps
{
Global.ScriptMgr.OnDestroyMap(this);
// Delete all waiting spawns
// This doesn't delete from database.
DeleteRespawnInfo();
for (var i = 0; i < i_worldObjects.Count; ++i)
{
WorldObject obj = i_worldObjects[i];
@@ -279,7 +285,7 @@ namespace Game.Maps
m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodOnContinents();
}
public void AddToGrid<T>(T obj, Cell cell)where T : WorldObject
public void AddToGrid<T>(T obj, Cell cell) where T : WorldObject
{
Grid grid = GetGrid(cell.GetGridX(), cell.GetGridY());
switch (obj.GetTypeId())
@@ -429,7 +435,7 @@ namespace Game.Maps
GridMaps[gx][gy] = m_parentMap.GridMaps[gx][gy];
++rootParentTerrainMap.GridMapReference[gx][gy];
}
}
}
}
}
@@ -475,6 +481,29 @@ namespace Game.Maps
loader.LoadN();
}
void GridMarkNoUnload(uint x, uint y)
{
// First make sure this grid is loaded
float gX = (((float)x - 0.5f - MapConst.CenterGridId) * MapConst.SizeofGrids) + (MapConst.CenterGridOffset * 2);
float gY = (((float)y - 0.5f - MapConst.CenterGridId) * MapConst.SizeofGrids) + (MapConst.CenterGridOffset * 2);
Cell cell = new Cell(gX, gY);
EnsureGridLoaded(cell);
// Mark as don't unload
var grid = GetGrid(x, y);
grid.SetUnloadExplicitLock(true);
}
void GridUnmarkNoUnload(uint x, uint y)
{
// If grid is loaded, clear unload lock
if (IsGridLoaded(new GridCoord(x, y)))
{
var grid = GetGrid(x, y);
grid.SetUnloadExplicitLock(false);
}
}
public void LoadGrid(float x, float y)
{
EnsureGridLoaded(new Cell(x, y));
@@ -598,10 +627,11 @@ namespace Game.Maps
return true;
}
public bool IsGridLoaded(float x, float y)
{
return IsGridLoaded(GridDefines.ComputeGridCoord(x, y));
}
public bool IsGridLoaded(uint gridId) { return IsGridLoaded(new GridCoord(gridId % MapConst.MaxGrids, gridId / MapConst.MaxGrids)); }
public bool IsGridLoaded(float x, float y) { return IsGridLoaded(GridDefines.ComputeGridCoord(x, y)); }
public bool IsGridLoaded(Position pos) { return IsGridLoaded(pos.GetPositionX(), pos.GetPositionY()); }
public bool IsGridLoaded(GridCoord p)
{
@@ -637,6 +667,20 @@ namespace Game.Maps
}
}
public void UpdatePlayerZoneStats(uint oldZone, uint newZone)
{
// Nothing to do if no change
if (oldZone == newZone)
return;
if (oldZone != MapConst.InvalidZone)
{
Cypher.Assert(_zonePlayerCountMap[oldZone] != 0, $"A player left zone {oldZone} (went to {newZone}) - but there were no players in the zone!");
--_zonePlayerCountMap[oldZone];
}
++_zonePlayerCountMap[newZone];
}
public virtual void Update(uint diff)
{
_dynamicTree.Update(diff);
@@ -653,9 +697,18 @@ namespace Game.Maps
}
}
/// process any due respawns
if (_respawnCheckTimer <= diff)
{
ProcessRespawns();
_respawnCheckTimer = WorldConfig.GetUIntValue(WorldCfg.RespawnMinCheckIntervalMs);
}
else
_respawnCheckTimer -= diff;
// update active cells around players and active objects
ResetMarkedCells();
var update = new UpdaterNotifier(diff);
var grid_object_update = new Visitor(update, GridMapTypeMask.AllGrid);
@@ -666,10 +719,10 @@ namespace Game.Maps
Player player = m_activePlayers[i];
if (!player.IsInWorld)
continue;
// update players at tick
player.Update(diff);
VisitNearbyCellsOf(player, grid_object_update, world_object_update);
// If player is using far sight or mind vision, visit that object too
@@ -698,7 +751,7 @@ namespace Game.Maps
VisitNearbyCellsOf(c, grid_object_update, world_object_update);
}
}
for (var i = 0; i < m_activeNonPlayers.Count; ++i)
{
WorldObject obj = m_activeNonPlayers[i];
@@ -707,7 +760,7 @@ namespace Game.Maps
VisitNearbyCellsOf(obj, grid_object_update, world_object_update);
}
for (var i = 0; i < _transports.Count; ++i)
{
Transport transport = _transports[i];
@@ -716,7 +769,7 @@ namespace Game.Maps
transport.Update(diff);
}
SendObjectUpdates();
// Process necessary scripts
@@ -841,6 +894,8 @@ namespace Game.Maps
public virtual void RemovePlayerFromMap(Player player, bool remove)
{
// Before leaving map, update zone/area for stats
player.UpdateZone(MapConst.InvalidZone, 0);
Global.ScriptMgr.OnPlayerLeaveMap(this, player);
player.GetHostileRefManager().DeleteReferences(); // multithreading crashfix
@@ -1167,7 +1222,7 @@ namespace Game.Maps
{
_creatureToMoveLock = true;
for (var i = 0; i< creaturesToMove.Count; ++i)
for (var i = 0; i < creaturesToMove.Count; ++i)
{
Creature creature = creaturesToMove[i];
if (creature.GetMap() != this) //pet is teleported to another map
@@ -1493,7 +1548,7 @@ namespace Game.Maps
// if in same cell then none do
if (old_cell.DiffCell(new_cell))
{
Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(),
Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(),
old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY());
RemoveFromGrid(at, old_cell);
@@ -1810,6 +1865,16 @@ namespace Game.Maps
return mapHeight; // explicitly use map data
}
public float GetHeight(PhaseShift phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch)
{
return Math.Max(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist));
}
public float GetHeight(PhaseShift phaseShift, Position pos, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch)
{
return GetHeight(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist);
}
public float GetMinHeight(PhaseShift phaseShift, float x, float y)
{
GridMap grid = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y);
@@ -1974,6 +2039,8 @@ namespace Game.Maps
return areaId;
}
public uint GetAreaId(PhaseShift phaseShift, Position pos) { return GetAreaId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); }
public uint GetZoneId(PhaseShift phaseShift, float x, float y, float z)
{
uint areaId = GetAreaId(phaseShift, x, y, z);
@@ -1985,6 +2052,8 @@ namespace Game.Maps
return areaId;
}
public uint GetZoneId(PhaseShift phaseShift, Position pos) { return GetZoneId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); }
public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, float x, float y, float z)
{
areaid = zoneid = GetAreaId(phaseShift, x, y, z);
@@ -1994,6 +2063,8 @@ namespace Game.Maps
zoneid = area.ParentAreaID;
}
public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, Position pos) { GetZoneAndAreaId(phaseShift, out zoneid, out areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); }
private byte GetTerrainType(PhaseShift phaseShift, float x, float y)
{
GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y);
@@ -2229,11 +2300,6 @@ namespace Game.Maps
return result;
}
public float GetHeight(PhaseShift phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch)
{
return Math.Max(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist));
}
public bool IsInWater(PhaseShift phaseShift, float x, float y, float pZ)
{
return Convert.ToBoolean(GetLiquidStatus(phaseShift, x, y, pZ, MapConst.MapAllLiquidTypes) & (ZLiquidStatus.InWater | ZLiquidStatus.UnderWater));
@@ -2379,6 +2445,349 @@ namespace Game.Maps
}
}
bool CheckRespawn(RespawnInfo info)
{
uint poolId = info.spawnId != 0 ? Global.PoolMgr.IsPartOfAPool(info.type, info.spawnId) : 0;
// First, check if there's already an instance of this object that would block the respawn
// Only do this for unpooled spawns
if (poolId == 0)
{
bool doDelete = false;
switch (info.type)
{
case SpawnObjectType.Creature:
{
// escort check for creatures only (if the world config boolean is set)
bool isEscort = false;
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && info.type == SpawnObjectType.Creature)
{
CreatureData cdata = Global.ObjectMgr.GetCreatureData(info.spawnId);
if (cdata != null)
if (cdata.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc))
isEscort = true;
}
var range = _creatureBySpawnIdStore.LookupByKey(info.spawnId);
foreach (var creature in range)
{
if (!creature.IsAlive())
continue;
// escort NPCs are allowed to respawn as long as all other instances are already escorting
if (isEscort && creature.IsEscortNPC(true))
continue;
doDelete = true;
break;
}
break;
}
case SpawnObjectType.GameObject:
// gameobject check is simpler - they cannot be dead or escorting
if (_gameobjectBySpawnIdStore.ContainsKey(info.spawnId))
doDelete = true;
break;
default:
Cypher.Assert(false, $"Invalid spawn type {info.type} with spawnId {info.spawnId} on map {GetId()}");
return true;
}
if (doDelete)
{
info.respawnTime = 0;
return false;
}
}
// next, check linked respawn time
ObjectGuid thisGUID = info.type == SpawnObjectType.GameObject ? ObjectGuid.Create(HighGuid.GameObject, GetId(), info.entry, info.spawnId) : ObjectGuid.Create(HighGuid.Creature, GetId(), info.entry, info.spawnId);
long linkedTime = GetLinkedRespawnTime(thisGUID);
if (linkedTime != 0)
{
long now = Time.UnixTime;
long respawnTime;
if (Global.ObjectMgr.GetLinkedRespawnGuid(thisGUID) == thisGUID) // never respawn, save "something" in DB
respawnTime = now + Time.Week;
else // set us to check again shortly after linked unit
respawnTime = Math.Max(now, linkedTime) + RandomHelper.URand(5, 15);
info.respawnTime = respawnTime;
return false;
}
// now, check if we're part of a pool
if (poolId != 0)
{
// ok, part of a pool - hand off to pool logic to handle this, we're just going to remove the respawn and call it a day
if (info.type == SpawnObjectType.GameObject)
Global.PoolMgr.UpdatePool<GameObject>(poolId, info.spawnId);
else if (info.type == SpawnObjectType.Creature)
Global.PoolMgr.UpdatePool<Creature>(poolId, info.spawnId);
else
Cypher.Assert(false, $"Invalid spawn type {info.type} (spawnid {info.spawnId}) on map {GetId()}");
info.respawnTime = 0;
return false;
}
// if we're a creature, see if the script objects to us spawning
if (info.type == SpawnObjectType.Creature)
{
if (!Global.ScriptMgr.CanSpawn(info.spawnId, info.entry, Global.ObjectMgr.GetCreatureData(info.spawnId), this))
{ // if a script blocks our respawn, schedule next check in a little bit
info.respawnTime = Time.UnixTime + RandomHelper.URand(4, 7);
return false;
}
}
return true;
}
void DoRespawn(SpawnObjectType type, ulong spawnId, uint gridId)
{
if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler
return;
switch (type)
{
case SpawnObjectType.Creature:
{
Creature obj = new Creature();
if (!obj.LoadFromDB(spawnId, this, true, true))
obj.Dispose();
break;
}
case SpawnObjectType.GameObject:
{
GameObject obj = new GameObject();
if (!obj.LoadFromDB(spawnId, this, true))
obj.Dispose();
break;
}
default:
Cypher.Assert(false, $"Invalid spawn type {type} (spawnid {spawnId}) on map {GetId()}");
break;
}
}
void Respawn(RespawnInfo info, bool force = false, SQLTransaction dbTrans = null)
{
if (!force && !CheckRespawn(info))
{
if (info.respawnTime != 0)
SaveRespawnTime(info.type, info.spawnId, info.entry, info.respawnTime, info.zoneId, info.gridId, true, true, dbTrans);
else
RemoveRespawnTime(info);
return;
}
// remove the actual respawn record first - since this deletes it, we save what we need
SpawnObjectType type = info.type;
uint gridId = info.gridId;
ulong spawnId = info.spawnId;
RemoveRespawnTime(info);
DoRespawn(type, spawnId, gridId);
}
void Respawn(List<RespawnInfo> respawnData, bool force, SQLTransaction dbTrans)
{
SQLTransaction trans = dbTrans != null ? dbTrans : new SQLTransaction();
foreach (RespawnInfo info in respawnData)
Respawn(info, force, trans);
if (dbTrans == null)
DB.Characters.CommitTransaction(trans);
}
void AddRespawnInfo(RespawnInfo info, bool replace)
{
if (info.spawnId == 0)
return;
var bySpawnIdMap = GetRespawnMapForType(info.type);
var existing = bySpawnIdMap.LookupByKey(info.spawnId);
if (existing != null) // spawnid already has a respawn scheduled
{
if (replace || info.respawnTime < existing.respawnTime) // delete existing in this case
DeleteRespawnInfo(existing);
else // don't delete existing, instead replace respawn time so caller saves the correct time
{
info.respawnTime = existing.respawnTime;
return;
}
}
// if we get to this point, we should insert the respawninfo (there either was no prior entry, or it was deleted already)
RespawnInfo ri = new RespawnInfo(info);
_respawnTimes.Add(ri);
bySpawnIdMap.Add(ri.spawnId, ri);
}
static void PushRespawnInfoFrom(List<RespawnInfo> data, Dictionary<ulong, RespawnInfo> map, uint zoneId)
{
foreach (var pair in map)
if (zoneId == 0 || pair.Value.zoneId == zoneId)
data.Add(pair.Value);
}
public void GetRespawnInfo(List<RespawnInfo> respawnData, SpawnObjectTypeMask types, uint zoneId)
{
if (types.HasAnyFlag(SpawnObjectTypeMask.Creature))
PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId, zoneId);
if (types.HasAnyFlag(SpawnObjectTypeMask.GameObject))
PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId, zoneId);
}
RespawnInfo GetRespawnInfo(SpawnObjectType type, ulong spawnId)
{
var map = GetRespawnMapForType(type);
var respawnInfo = map.LookupByKey(spawnId);
if (respawnInfo == null)
return null;
return respawnInfo;
}
Dictionary<ulong, RespawnInfo> GetRespawnMapForType(SpawnObjectType type) { return (type == SpawnObjectType.GameObject) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; }
void DeleteRespawnInfo() // delete everything
{
_respawnTimes.Clear();
_creatureRespawnTimesBySpawnId.Clear();
_gameObjectRespawnTimesBySpawnId.Clear();
}
void DeleteRespawnInfo(RespawnInfo info)
{
// Delete from all relevant containers to ensure consistency
Cypher.Assert(info != null);
// spawnid store
GetRespawnMapForType(info.type).Remove(info.spawnId);
Cypher.Assert(GetRespawnMapForType(info.type).Count == 1, $"Respawn stores inconsistent for map {GetId()}, spawnid {info.spawnId} (type {info.type})");
//respawn heap
_respawnTimes.Remove(info);
}
public void RemoveRespawnTime(RespawnInfo info, bool doRespawn = false, SQLTransaction dbTrans = null)
{
PreparedStatement stmt;
switch (info.type)
{
case SpawnObjectType.Creature:
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN);
break;
case SpawnObjectType.GameObject:
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN);
break;
default:
Cypher.Assert(false, $"Invalid respawninfo type {info.type} for spawnid {info.spawnId} map {GetId()}");
return;
}
stmt.AddValue(0, info.spawnId);
stmt.AddValue(1, GetId());
stmt.AddValue(2, GetInstanceId());
DB.Characters.ExecuteOrAppend(dbTrans, stmt);
if (doRespawn)
Respawn(info);
else
DeleteRespawnInfo(info);
}
public void RemoveRespawnTime(List<RespawnInfo> respawnData, bool doRespawn, SQLTransaction dbTrans = null)
{
SQLTransaction trans = dbTrans != null ? dbTrans : new SQLTransaction();
foreach (RespawnInfo info in respawnData)
RemoveRespawnTime(info, doRespawn, trans);
if (dbTrans == null)
DB.Characters.CommitTransaction(trans);
}
public void RemoveRespawnTime(SpawnObjectTypeMask types = SpawnObjectTypeMask.All, uint zoneId = 0, bool doRespawn = false, SQLTransaction dbTrans = null)
{
List<RespawnInfo> v = new List<RespawnInfo>();
GetRespawnInfo(v, types, zoneId);
if (!v.Empty())
RemoveRespawnTime(v, doRespawn, dbTrans);
}
public void RemoveRespawnTime(SpawnObjectType type, ulong spawnId, bool doRespawn = false, SQLTransaction dbTrans = null)
{
RespawnInfo info = GetRespawnInfo(type, spawnId);
if (info != null)
RemoveRespawnTime(info, doRespawn, dbTrans);
}
void ProcessRespawns()
{
long now = Time.UnixTime;
while (!_respawnTimes.Empty())
{
RespawnInfo next = _respawnTimes.First();
if (now < next.respawnTime) // done for this tick
break;
if (CheckRespawn(next)) // see if we're allowed to respawn
{
// ok, respawn
_respawnTimes.Remove(next);
GetRespawnMapForType(next.type).Remove(next.spawnId);
DoRespawn(next.type, next.spawnId, next.gridId);
}
else if (next.respawnTime == 0) // just remove respawn entry without rescheduling
{
_respawnTimes.Remove(next);
GetRespawnMapForType(next.type).Remove(next.spawnId);
}
else // value changed, update heap position
{
Cypher.Assert(now < next.respawnTime); // infinite loop guard
//_respawnTimes.decrease(next->handle);
}
}
}
public void ApplyDynamicModeRespawnScaling(WorldObject obj, ulong spawnId, ref uint respawnDelay, uint mode)
{
Cypher.Assert(mode == 1);
Cypher.Assert(obj.GetMap() == this);
SpawnObjectType type;
switch (obj.GetTypeId())
{
case TypeId.Unit:
type = SpawnObjectType.Creature;
break;
case TypeId.GameObject:
type = SpawnObjectType.GameObject;
break;
default:
return;
}
SpawnData data = Global.ObjectMgr.GetSpawnData(type, spawnId);
if (data == null || !data.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.DynamicSpawnRate))
return;
if (!_zonePlayerCountMap.ContainsKey(obj.GetZoneId()))
return;
uint playerCount = _zonePlayerCountMap[obj.GetZoneId()];
if (playerCount == 0)
return;
double adjustFactor = WorldConfig.GetFloatValue(type == SpawnObjectType.GameObject ? WorldCfg.RespawnDynamicRateGameobject : WorldCfg.RespawnDynamicRateCreature) / playerCount;
if (adjustFactor >= 1.0) // nothing to do here
return;
uint timeMinimum = WorldConfig.GetUIntValue(type == SpawnObjectType.GameObject ? WorldCfg.RespawnDynamicMinimumGameObject : WorldCfg.RespawnDynamicMinimumCreature);
if (respawnDelay <= timeMinimum)
return;
respawnDelay = (uint)Math.Max(Math.Ceiling(respawnDelay * adjustFactor), timeMinimum);
}
public virtual void DelayedUpdate(uint diff)
{
for (var i = 0; i < _transports.Count; ++i)
@@ -2617,64 +3026,37 @@ namespace Game.Maps
m_activeNonPlayers.Remove(obj);
}
public void SaveCreatureRespawnTime(ulong dbGuid, long respawnTime)
public void SaveRespawnTime(SpawnObjectType type, ulong spawnId, uint entry, long respawnTime, uint zoneId, uint gridId = 0, bool writeDB = true, bool replace = false, SQLTransaction dbTrans = null)
{
if (respawnTime == 0)
{
// Delete only
RemoveCreatureRespawnTime(dbGuid);
RemoveRespawnTime(type, spawnId, false, dbTrans);
return;
}
_creatureRespawnTimes[dbGuid] = respawnTime;
RespawnInfo ri = new RespawnInfo();
ri.type = type;
ri.spawnId = spawnId;
ri.entry = entry;
ri.respawnTime = respawnTime;
ri.gridId = gridId;
ri.zoneId = zoneId;
AddRespawnInfo(ri, replace);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CREATURE_RESPAWN);
stmt.AddValue(0, dbGuid);
if (writeDB)
SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace
}
public void SaveRespawnTimeDB(SpawnObjectType type, ulong spawnId, long respawnTime, SQLTransaction dbTrans = null)
{
// Just here for support of compatibility mode
PreparedStatement stmt = DB.Characters.GetPreparedStatement((type == SpawnObjectType.GameObject) ? CharStatements.REP_GO_RESPAWN : CharStatements.REP_CREATURE_RESPAWN);
stmt.AddValue(0, spawnId);
stmt.AddValue(1, respawnTime);
stmt.AddValue(2, GetId());
stmt.AddValue(3, GetInstanceId());
DB.Characters.Execute(stmt);
}
public void RemoveCreatureRespawnTime(ulong dbGuid)
{
_creatureRespawnTimes.Remove(dbGuid);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN);
stmt.AddValue(0, dbGuid);
stmt.AddValue(1, GetId());
stmt.AddValue(2, GetInstanceId());
DB.Characters.Execute(stmt);
}
public void SaveGORespawnTime(ulong dbGuid, long respawnTime)
{
if (respawnTime == 0)
{
// Delete only
RemoveGORespawnTime(dbGuid);
return;
}
_goRespawnTimes[dbGuid] = respawnTime;
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GO_RESPAWN);
stmt.AddValue(0, dbGuid);
stmt.AddValue(1, respawnTime);
stmt.AddValue(2, GetId());
stmt.AddValue(3, GetInstanceId());
DB.Characters.Execute(stmt);
}
public void RemoveGORespawnTime(ulong dbGuid)
{
_goRespawnTimes.Remove(dbGuid);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN);
stmt.AddValue(0, dbGuid);
stmt.AddValue(1, GetId());
stmt.AddValue(2, GetInstanceId());
DB.Characters.Execute(stmt);
DB.Characters.ExecuteOrAppend(dbTrans, stmt);
}
public void LoadRespawnTimes()
@@ -2690,7 +3072,10 @@ namespace Game.Maps
var loguid = result.Read<uint>(0);
var respawnTime = result.Read<long>(1);
_creatureRespawnTimes[loguid] = respawnTime;
CreatureData cdata = Global.ObjectMgr.GetCreatureData(loguid);
if (cdata != null)
SaveRespawnTime(SpawnObjectType.Creature, loguid, cdata.Id, respawnTime, GetZoneId(PhasingHandler.EmptyPhaseShift, cdata.spawnPoint), GridDefines.ComputeGridCoord(cdata.spawnPoint.GetPositionX(), cdata.spawnPoint.GetPositionY()).GetId(), false);
} while (result.NextRow());
}
@@ -2705,16 +3090,19 @@ namespace Game.Maps
var loguid = result.Read<uint>(0);
var respawnTime = result.Read<long>(1);
_goRespawnTimes[loguid] = respawnTime;
GameObjectData godata = Global.ObjectMgr.GetGameObjectData(loguid);
if (godata != null)
SaveRespawnTime(SpawnObjectType.GameObject, loguid, godata.Id, respawnTime, GetZoneId(PhasingHandler.EmptyPhaseShift, godata.spawnPoint), GridDefines.ComputeGridCoord(godata.spawnPoint.GetPositionX(), godata.spawnPoint.GetPositionY()).GetId(), false);
} while (result.NextRow());
}
}
public long GetRespawnTime(SpawnObjectType type, ulong spawnId) { return (type == SpawnObjectType.GameObject) ? GetGORespawnTime(spawnId) : GetCreatureRespawnTime(spawnId); }
public void DeleteRespawnTimes()
{
_creatureRespawnTimes.Clear();
_goRespawnTimes.Clear();
DeleteRespawnInfo();
DeleteRespawnTimesInDB(GetId(), GetInstanceId());
}
@@ -3106,7 +3494,8 @@ namespace Game.Maps
return GetGrid(p.X_coord, p.Y_coord) == null ||
GetGrid(p.X_coord, p.Y_coord).GetGridState() == GridState.Removal;
}
public bool IsRemovalGrid(Position pos) { return IsRemovalGrid(pos.GetPositionX(), pos.GetPositionY()); }
private bool GetUnloadLock(GridCoord p)
{
return GetGrid(p.X_coord, p.Y_coord).GetUnloadLock();
@@ -3320,7 +3709,7 @@ namespace Game.Maps
{
return _dynamicTree.GetHeight(x, y, z, maxSearchDist, phaseShift);
}
public virtual uint GetOwnerGuildId(Team team = Team.Other)
{
return 0;
@@ -3328,12 +3717,12 @@ namespace Game.Maps
public long GetCreatureRespawnTime(ulong dbGuid)
{
return _creatureRespawnTimes.LookupByKey(dbGuid);
return _creatureRespawnTimesBySpawnId.ContainsKey(dbGuid) ? _creatureRespawnTimesBySpawnId[dbGuid].respawnTime : 0;
}
public long GetGORespawnTime(ulong dbGuid)
{
return _goRespawnTimes.LookupByKey(dbGuid);
return _gameObjectRespawnTimesBySpawnId.ContainsKey(dbGuid) ? _gameObjectRespawnTimesBySpawnId[dbGuid].respawnTime : 0;
}
void SetTimer(uint t)
@@ -3421,6 +3810,33 @@ namespace Game.Maps
return go ? go.ToTransport() : null;
}
Creature GetCreatureBySpawnId(ulong spawnId)
{
var bounds = GetCreatureBySpawnIdStore().LookupByKey(spawnId);
if (bounds.Empty())
return null;
var foundCreature = bounds.Find(creature => creature.IsAlive());
return foundCreature != null ? foundCreature : bounds[0];
}
GameObject GetGameObjectBySpawnId(ulong spawnId)
{
var bounds = GetGameObjectBySpawnIdStore().LookupByKey(spawnId);
if (bounds.Empty())
return null;
var foundGameObject = bounds.Find(gameobject => gameobject.IsSpawned());
return foundGameObject != null ? foundGameObject : bounds[0];
}
public WorldObject GetWorldObjectBySpawnId(SpawnObjectType type, ulong spawnId)
{
return type == SpawnObjectType.GameObject ? (WorldObject)GetGameObjectBySpawnId(spawnId) : (WorldObject)GetCreatureBySpawnId(spawnId);
}
public void Visit(Cell cell, Visitor visitor)
{
uint x = cell.GetGridX();
@@ -3507,7 +3923,7 @@ namespace Game.Maps
return null;
}
if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, entry, pos.posX, pos.posY, pos.posZ, pos.Orientation, null, vehId))
if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, entry, pos, null, vehId))
return null;
// Set the summon to the summoner's phase
@@ -3554,7 +3970,7 @@ namespace Game.Maps
_updateObjects.Remove(obj);
}
public static implicit operator bool (Map map)
public static implicit operator bool(Map map)
{
return map != null;
}
@@ -4178,7 +4594,7 @@ namespace Game.Maps
uTarget = target?.ToUnit();
break;
case eScriptFlags.CastspellSourceToSource: // source . source
uSource =source?.ToUnit();
uSource = source?.ToUnit();
uTarget = uSource;
break;
case eScriptFlags.CastspellTargetToTarget: // target . target
@@ -4442,10 +4858,14 @@ namespace Game.Maps
GridMap[][] GridMaps = new GridMap[MapConst.MaxGrids][];
ushort[][] GridMapReference = new ushort[MapConst.MaxGrids][];
Dictionary<ulong, long> _creatureRespawnTimes = new Dictionary<ulong, long>();
DynamicMapTree _dynamicTree = new DynamicMapTree();
Dictionary<ulong, long> _goRespawnTimes = new Dictionary<ulong, long>();
SortedSet<RespawnInfo> _respawnTimes = new SortedSet<RespawnInfo>(new CompareRespawnInfo());
Dictionary<ulong, RespawnInfo> _creatureRespawnTimesBySpawnId = new Dictionary<ulong, RespawnInfo>();
Dictionary<ulong, RespawnInfo> _gameObjectRespawnTimesBySpawnId = new Dictionary<ulong, RespawnInfo>();
uint _respawnCheckTimer;
Dictionary<uint, uint> _zonePlayerCountMap = new Dictionary<uint, uint>();
List<Transport> _transports = new List<Transport>();
Grid[][] i_grids = new Grid[MapConst.MaxGrids][];
MapRecord i_mapRecord;
@@ -5050,4 +5470,39 @@ namespace Game.Maps
public Optional<AreaInfo> areaInfo;
public Optional<LiquidData> LiquidInfo;
}
public class RespawnInfo
{
public SpawnObjectType type;
public ulong spawnId;
public uint entry;
public long respawnTime;
public uint gridId;
public uint zoneId;
public RespawnInfo() { }
public RespawnInfo(RespawnInfo info)
{
type = info.type;
spawnId = info.spawnId;
entry = info.entry;
respawnTime = info.respawnTime;
gridId = info.gridId;
zoneId = info.zoneId;
}
}
struct CompareRespawnInfo : IComparer<RespawnInfo>
{
public int Compare(RespawnInfo a, RespawnInfo b)
{
if (a.respawnTime != b.respawnTime)
return a.respawnTime.CompareTo(b.respawnTime);
if (a.spawnId != b.spawnId)
return a.spawnId.CompareTo(b.spawnId);
Cypher.Assert(a.type != b.type, $"Duplicate respawn entry for spawnId ({a.type},{a.spawnId}) found!");
return a.type.CompareTo(b.type);
}
}
}
+10
View File
@@ -430,6 +430,16 @@ namespace Game.Entities
m.GetZoneAndAreaId(phaseShift, out zoneid, out areaid, x, y, z);
}
public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, uint mapid, Position pos)
{
GetZoneAndAreaId(phaseShift, out zoneid, out areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
}
public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, WorldLocation loc)
{
GetZoneAndAreaId(phaseShift, out zoneid, out areaid, loc.GetMapId(), loc);
}
public void DoForAllMaps(Action<Map> worker)
{
lock (_mapsLock)
+39 -3
View File
@@ -80,10 +80,46 @@ namespace Game.Maps
foreach (var guid in guid_set)
{
T obj = new T();
if (!obj.LoadFromDB(guid, map))
continue;
// Don't spawn at all if there's a respawn time
if ((obj.IsTypeId(TypeId.Unit) && map.GetCreatureRespawnTime(guid) == 0) || (obj.IsTypeId(TypeId.GameObject) && map.GetGORespawnTime(guid) == 0))
{
//TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid);
if (obj.IsTypeId(TypeId.Unit))
{
CreatureData cdata = Global.ObjectMgr.GetCreatureData(guid);
Cypher.Assert(cdata != null, $"Tried to load creature with spawnId {guid}, but no such creature exists.");
AddObjectHelper(cell, ref count, map, obj);
SpawnGroupTemplateData group = cdata.spawnGroupData;
// If creature in manual spawn group, don't spawn here, unless group is already active.
if (group.flags.HasFlag(SpawnGroupFlags.ManualSpawn) && !group.isActive)
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, Time.UnixTime + RandomHelper.URand(4, 7), map.GetZoneId(PhasingHandler.EmptyPhaseShift, cdata.spawnPoint), GridDefines.ComputeGridCoord(cdata.spawnPoint.GetPositionX(), cdata.spawnPoint.GetPositionY()).GetId(), false);
continue;
}
}
else if (obj.IsTypeId(TypeId.GameObject))
{
// If gameobject in manual spawn group, don't spawn here, unless group is already active.
GameObjectData godata = Global.ObjectMgr.GetGameObjectData(guid);
Cypher.Assert(godata != null, $"Tried to load gameobject with spawnId {guid}, but no such object exists.");
if (godata.spawnGroupData.flags.HasFlag(SpawnGroupFlags.ManualSpawn) && !godata.spawnGroupData.isActive)
continue;
}
if (!obj.LoadFromDB(guid, map, false, false))
{
obj.Dispose();
continue;
}
AddObjectHelper(cell, ref count, map, obj);
}
else
obj.Dispose();
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; 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, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Entities;
using System.Collections.Generic;
namespace Game.Maps
{
public class SpawnGroupTemplateData
{
public uint groupId;
public string name;
public uint mapId;
public SpawnGroupFlags flags;
public bool isActive;
}
public class SpawnData
{
public SpawnObjectType type;
public ulong spawnId;
public uint Id; // entry in respective _template table
public WorldLocation spawnPoint;
public PhaseUseFlagsValues phaseUseFlags;
public uint phaseId;
public uint phaseGroup;
public int terrainSwapMap = -1;
public int spawntimesecs;
public List<Difficulty> spawnDifficulties;
public SpawnGroupTemplateData spawnGroupData;
public uint ScriptId;
public bool dbData = true;
public SpawnData(SpawnObjectType t)
{
type = t;
}
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ namespace Game.Maps
{
public class ZoneScript
{
public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.id; }
public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.Id; }
public virtual uint GetGameObjectEntry(ulong spawnId, uint entry) { return entry; }
public virtual void OnCreatureCreate(Creature creature) { }
+5 -5
View File
@@ -358,7 +358,7 @@ namespace Game.PvP
void AddGO(uint type, ulong guid)
{
GameObjectData data = Global.ObjectMgr.GetGOData(guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid);
if (data == null)
return;
@@ -378,7 +378,7 @@ namespace Game.PvP
public bool AddObject(uint type, uint entry, uint map, Position pos, Quaternion rot)
{
ulong guid = Global.ObjectMgr.AddGOData(entry, map, pos, rot, 0);
ulong guid = Global.ObjectMgr.AddGameObjectData(entry, map, pos, rot, 0);
if (guid != 0)
{
AddGO(type, guid);
@@ -412,7 +412,7 @@ namespace Game.PvP
return false;
}
m_capturePointSpawnId = Global.ObjectMgr.AddGOData(entry, map, pos, rot, 0);
m_capturePointSpawnId = Global.ObjectMgr.AddGameObjectData(entry, map, pos, rot, 0);
if (m_capturePointSpawnId == 0)
return false;
@@ -472,7 +472,7 @@ namespace Game.PvP
gameobject.Delete();
}
Global.ObjectMgr.DeleteGOData(spawnId);
Global.ObjectMgr.DeleteGameObjectData(spawnId);
m_ObjectTypes.Remove(spawnId);
m_Objects.Remove(type);
return true;
@@ -480,7 +480,7 @@ namespace Game.PvP
bool DelCapturePoint()
{
Global.ObjectMgr.DeleteGOData(m_capturePointSpawnId);
Global.ObjectMgr.DeleteGameObjectData(m_capturePointSpawnId);
m_capturePointSpawnId = 0;
if (m_capturePoint)
+37 -10
View File
@@ -144,14 +144,14 @@ namespace Game
uint pool_id = result.Read<uint>(1);
float chance = result.Read<float>(2);
GameObjectData data = Global.ObjectMgr.GetGOData(guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid);
if (data == null)
{
Log.outError(LogFilter.Sql, "`pool_gameobject` has a non existing gameobject spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id);
continue;
}
GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.id);
GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.Id);
if (goinfo.type != GameObjectTypes.Chest &&
goinfo.type != GameObjectTypes.FishingHole &&
goinfo.type != GameObjectTypes.GatheringNode &&
@@ -534,6 +534,21 @@ namespace Game
return 0;
}
// Selects proper template overload to call based on passed type
public uint IsPartOfAPool(SpawnObjectType type, ulong spawnId)
{
switch (type)
{
case SpawnObjectType.Creature:
return IsPartOfAPool<Creature>(spawnId);
case SpawnObjectType.GameObject:
return IsPartOfAPool<GameObject>(spawnId);
default:
Cypher.Assert(false, $"Invalid spawn type {type} passed to PoolMgr.IsPartOfPool (with spawnId {spawnId})");
return 0;
}
}
public enum QuestTypes
{
None = 0,
@@ -655,28 +670,40 @@ namespace Game
if (data != null)
{
Global.ObjectMgr.RemoveCreatureFromGrid(guid, data);
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
if (map != null && !map.Instanceable())
{
var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid);
foreach (var creature in creatureBounds)
{
// For dynamic spawns, save respawn time here
if (!creature.GetRespawnCompatibilityMode())
creature.SaveRespawnTime(0, false);
creature.AddObjectToRemoveList();
}
}
}
break;
}
case "GameObject":
{
var data = Global.ObjectMgr.GetGOData(guid);
var data = Global.ObjectMgr.GetGameObjectData(guid);
if (data != null)
{
Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data);
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
if (map != null && !map.Instanceable())
{
var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid);
foreach (var go in gameobjectBounds)
{
// For dynamic spawns, save respawn time here
if (!go.GetRespawnCompatibilityMode())
go.SaveRespawnTime(0, false);
go.AddObjectToRemoveList();
}
}
}
break;
@@ -863,24 +890,24 @@ namespace Game
Global.ObjectMgr.AddCreatureToGrid(obj.guid, data);
// Spawn if necessary (loaded grids only)
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
// We use spawn coords to spawn
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY))
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint))
Creature.CreateCreatureFromDB(obj.guid, map);
}
}
break;
case "GameObject":
{
GameObjectData data = Global.ObjectMgr.GetGOData(obj.guid);
GameObjectData data = Global.ObjectMgr.GetGameObjectData(obj.guid);
if (data != null)
{
Global.ObjectMgr.AddGameObjectToGrid(obj.guid, data);
// Spawn if necessary (loaded grids only)
// this base map checked as non-instanced and then only existed
Map map = Global.MapMgr.FindMap(data.mapid, 0);
Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0);
// We use current coords to unspawn, not spawn coords since creature can have changed grid
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY))
if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint))
{
GameObject go = GameObject.CreateGameObjectFromDB(obj.guid, map, false);
if (go)
+27 -4
View File
@@ -682,13 +682,36 @@ namespace Game.Scripting
}
//CreatureScript
public bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate actTemplate, CreatureData cData, Map map)
public bool CanSpawn(ulong spawnId, uint entry, CreatureData cData, Map map)
{
Cypher.Assert(actTemplate != null);
Cypher.Assert(map != null);
CreatureTemplate baseTemplate = Global.ObjectMgr.GetCreatureTemplate(entry);
if (baseTemplate == null)
baseTemplate = actTemplate;
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)
+43
View File
@@ -776,6 +776,49 @@ namespace Game
Values[WorldCfg.NoGrayAggroBelow] = Values[WorldCfg.NoGrayAggroAbove];
}
// Respawn Settings
Values[WorldCfg.RespawnMinCheckIntervalMs] = GetDefaultValue("Respawn.MinCheckIntervalMS", 5000);
Values[WorldCfg.RespawnDynamicMode] = GetDefaultValue("Respawn.DynamicMode", 0);
if ((int)Values[WorldCfg.RespawnDynamicMode] > 1)
{
Log.outError(LogFilter.ServerLoading, $"Invalid value for Respawn.DynamicMode ({Values[WorldCfg.RespawnDynamicMode]}). Set to 0.");
Values[WorldCfg.RespawnDynamicMode] = 0;
}
Values[WorldCfg.RespawnDynamicEscortNpc] = GetDefaultValue("Respawn.DynamicEscortNPC", true);
Values[WorldCfg.RespawnGuidWarnLevel] = GetDefaultValue("Respawn.GuidWarnLevel", 12000000);
if ((int)Values[WorldCfg.RespawnGuidWarnLevel] > 16777215)
{
Log.outError(LogFilter.ServerLoading, $"Respawn.GuidWarnLevel ({Values[WorldCfg.RespawnGuidWarnLevel]}) cannot be greater than maximum GUID (16777215). Set to 12000000.");
Values[WorldCfg.RespawnGuidWarnLevel] = 12000000;
}
Values[WorldCfg.RespawnGuidAlertLevel] = GetDefaultValue("Respawn.GuidAlertLevel", 16000000);
if ((int)Values[WorldCfg.RespawnGuidAlertLevel] > 16777215)
{
Log.outError(LogFilter.ServerLoading, $"Respawn.GuidWarnLevel ({Values[WorldCfg.RespawnGuidAlertLevel]}) cannot be greater than maximum GUID (16777215). Set to 16000000.");
Values[WorldCfg.RespawnGuidAlertLevel] = 16000000;
}
Values[WorldCfg.RespawnRestartQuietTime] = GetDefaultValue("Respawn.RestartQuietTime", 3);
if ((int)Values[WorldCfg.RespawnRestartQuietTime] > 23)
{
Log.outError(LogFilter.ServerLoading, $"Respawn.RestartQuietTime ({Values[WorldCfg.RespawnRestartQuietTime]}) must be an hour, between 0 and 23. Set to 3.");
Values[WorldCfg.RespawnRestartQuietTime] = 3;
}
Values[WorldCfg.RespawnDynamicRateCreature] = GetDefaultValue("Respawn.DynamicRateCreature", 10.0f);
if ((float)Values[WorldCfg.RespawnDynamicRateCreature] < 0.0f)
{
Log.outError(LogFilter.ServerLoading, $"Respawn.DynamicRateCreature ({Values[WorldCfg.RespawnDynamicRateCreature]}) must be positive. Set to 10.");
Values[WorldCfg.RespawnDynamicRateCreature] = 10.0f;
}
Values[WorldCfg.RespawnDynamicMinimumCreature] = GetDefaultValue("Respawn.DynamicMinimumCreature", 10);
Values[WorldCfg.RespawnDynamicRateGameobject] = GetDefaultValue("Respawn.DynamicRateGameObject", 10.0f);
if ((float)Values[WorldCfg.RespawnDynamicRateGameobject] < 0.0f)
{
Log.outError(LogFilter.ServerLoading, $"Respawn.DynamicRateGameObject ({Values[WorldCfg.RespawnDynamicRateGameobject]}) must be positive. Set to 10.");
Values[WorldCfg.RespawnDynamicRateGameobject] = 10.0f;
}
Values[WorldCfg.RespawnDynamicMinimumGameObject] = GetDefaultValue("Respawn.DynamicMinimumGameObject", 10);
Values[WorldCfg.RespawnGuidWarningFrequency] = GetDefaultValue("Respawn.WarningFrequency", 1800);
Values[WorldCfg.EnableMmaps] = GetDefaultValue("mmap.EnablePathFinding", false);
Values[WorldCfg.VmapIndoorCheck] = GetDefaultValue("vmap.EnableIndoorCheck", false);
+89 -1
View File
@@ -49,6 +49,7 @@ namespace Game
_realm = new Realm();
_worldUpdateTime = new WorldUpdateTime();
_warnShutdownTime = Time.UnixTime;
}
public Player FindPlayerInZone(uint zone)
@@ -92,6 +93,61 @@ namespace Game
return m_motd;
}
public void TriggerGuidWarning()
{
// Lock this only to prevent multiple maps triggering at the same time
lock (_guidAlertLock)
{
long gameTime = GameTime.GetGameTime();
long today = (gameTime / Time.Day) * Time.Day;
// Check if our window to restart today has passed. 5 mins until quiet time
while (gameTime >= (today + (WorldConfig.GetIntValue(WorldCfg.RespawnRestartQuietTime) * Time.Hour) - 1810))
today += Time.Day;
// Schedule restart for 30 minutes before quiet time, or as long as we have
_warnShutdownTime = today + (WorldConfig.GetIntValue(WorldCfg.RespawnRestartQuietTime) * Time.Hour) - 1800;
_guidWarn = true;
SendGuidWarning();
}
}
public void TriggerGuidAlert()
{
// Lock this only to prevent multiple maps triggering at the same time
lock (_guidAlertLock)
{
DoGuidAlertRestart();
_guidAlert = true;
_guidWarn = false;
}
}
void DoGuidWarningRestart()
{
if (m_ShutdownTimer != 0)
return;
ShutdownServ(1800, ShutdownMask.Restart, ShutdownExitCode.Restart);
_warnShutdownTime += Time.Hour;
}
void DoGuidAlertRestart()
{
if (m_ShutdownTimer != 0)
return;
ShutdownServ(300, ShutdownMask.Restart, ShutdownExitCode.Restart, _alertRestartReason);
}
void SendGuidWarning()
{
if (m_ShutdownTimer == 0 && _guidWarn && WorldConfig.GetIntValue(WorldCfg.RespawnGuidWarningFrequency) > 0)
SendServerMessage(ServerMessageType.String, _guidWarningMsg);
_warnDiff = 0;
}
public WorldSession FindSession(uint id)
{
return m_sessions.LookupByKey(id);
@@ -527,6 +583,9 @@ namespace Game
Log.outInfo(LogFilter.ServerLoading, "Loading Creature Base Stats...");
Global.ObjectMgr.LoadCreatureClassLevelStats();
Log.outInfo(LogFilter.ServerLoading, "Loading Spawn Group Templates...");
Global.ObjectMgr.LoadSpawnGroupTemplates();
Log.outInfo(LogFilter.ServerLoading, "Loading Creature Data...");
Global.ObjectMgr.LoadCreatures();
@@ -543,7 +602,10 @@ namespace Game
Global.ObjectMgr.LoadCreatureAddons();
Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects...");
Global.ObjectMgr.LoadGameobjects();
Global.ObjectMgr.LoadGameObjects();
Log.outInfo(LogFilter.ServerLoading, "Loading Spawn Group Data...");
Global.ObjectMgr.LoadSpawnGroups();
Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Addon Data...");
Global.ObjectMgr.LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects()
@@ -1083,6 +1145,9 @@ namespace Game
m_visibility_notify_periodInInstances = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InInstances", SharedConst.DefaultVisibilityNotifyPeriod);
m_visibility_notify_periodInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InBGArenas", SharedConst.DefaultVisibilityNotifyPeriod);
_guidWarningMsg = WorldConfig.GetDefaultValue("Respawn.WarningMessage", "There will be an unscheduled server restart at 03:00. The server will be available again shortly after.");
_alertRestartReason = WorldConfig.GetDefaultValue("Respawn.AlertRestartReason", "Urgent Maintenance");
string dataPath = ConfigMgr.GetDefaultValue("DataDir", "./");
if (reload)
{
@@ -1332,6 +1397,16 @@ namespace Game
// update the instance reset times
Global.InstanceSaveMgr.Update();
// Check for shutdown warning
if (_guidWarn && !_guidAlert)
{
_warnDiff += diff;
if (GameTime.GetGameTime() >= _warnShutdownTime)
DoGuidWarningRestart();
else if (_warnDiff > WorldConfig.GetIntValue(WorldCfg.RespawnGuidWarningFrequency) * Time.InMilliseconds)
SendGuidWarning();
}
Global.ScriptMgr.OnWorldUpdate(diff);
}
@@ -2288,6 +2363,9 @@ namespace Game
public CleaningFlags GetCleaningFlags() { return m_CleaningFlags; }
public void SetCleaningFlags(CleaningFlags flags) { m_CleaningFlags = flags; }
public bool IsGuidWarning() { return _guidWarn; }
public bool IsGuidAlert() { return _guidAlert; }
public WorldUpdateTime GetWorldUpdateTime() { return _worldUpdateTime; }
#region Fields
@@ -2349,6 +2427,16 @@ namespace Game
string _dataPath;
WorldUpdateTime _worldUpdateTime;
string _guidWarningMsg;
string _alertRestartReason;
object _guidAlertLock = new object();
bool _guidWarn;
bool _guidAlert;
uint _warnDiff;
long _warnShutdownTime;
#endregion
}
@@ -96,7 +96,7 @@ namespace Scripts.Northrend.IcecrownCitadel
CreatureData data = creature.GetCreatureData();
if (data != null)
creature.UpdatePosition(data.posX, data.posY, data.posZ, data.orientation);
creature.UpdatePosition(data.spawnPoint);
creature.DespawnOrUnsummon();
creature.SetCorpseDelay(corpseDelay);
@@ -496,7 +496,7 @@ namespace Scripts.Northrend.IcecrownCitadel
Creature crusader = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CaptainArnath + i));
if (crusader)
{
if (crusader.IsAlive() && crusader.GetEntry() == crusader.GetCreatureData().id)
if (crusader.IsAlive() && crusader.GetEntry() == crusader.GetCreatureData().Id)
{
crusader.m_Events.AddEvent(new CaptainSurviveTalk(crusader), crusader.m_Events.CalculateTime(delay));
delay += 6000;
@@ -346,7 +346,7 @@ namespace Scripts.Northrend.IcecrownCitadel
// Weekly quest spawn prevention
public override uint GetCreatureEntry(ulong guidLow, CreatureData data)
{
uint entry = data.id;
uint entry = data.Id;
switch (entry)
{
case CreatureIds.InfiltratorMinchar:
@@ -384,13 +384,13 @@ namespace Scripts.Northrend.IcecrownCitadel
case CreatureIds.ZafodBoombox:
GameObjectTemplate go = Global.ObjectMgr.GetGameObjectTemplate(GameObjectIds.TheSkybreaker_A);
if (go != null)
if ((TeamInInstance == Team.Alliance && data.mapid == go.MoTransport.SpawnMap) ||
(TeamInInstance == Team.Horde && data.mapid != go.MoTransport.SpawnMap))
if ((TeamInInstance == Team.Alliance && data.spawnPoint.GetMapId() == go.MoTransport.SpawnMap) ||
(TeamInInstance == Team.Horde && data.spawnPoint.GetMapId() != go.MoTransport.SpawnMap))
return entry;
return 0;
case CreatureIds.IGBMuradinBrozebeard:
if ((TeamInInstance == Team.Alliance && data.posX > 10.0f) ||
(TeamInInstance == Team.Horde && data.posX < 10.0f))
if ((TeamInInstance == Team.Alliance && data.spawnPoint.GetPositionX() > 10.0f) ||
(TeamInInstance == Team.Horde && data.spawnPoint.GetPositionX() < 10.0f))
return entry;
return 0;
default:
+116
View File
@@ -1661,6 +1661,122 @@ MonsterSight = 50.000000
#
###################################################################################################
###################################################################################################
# SPAWN/RESPAWN SETTINGS
#
# Respawn.MinCheckIntervalMS
# Description: Minimum time that needs to pass between respawn checks for any given map.
# Default: 5000 - 5 seconds
Respawn.MinCheckIntervalMS = 5000
#
# Respawn.GuidWarnLevel
# Description: The point at which the highest guid for creatures or gameobjects in any map must reach
# before the warning logic is enabled. A restart will then be queued at the next quiet time
# The maximum guid per map is 16,777,216. So, it must be less than this value.
# Default: 12000000 - 12 million
Respawn.GuidWarnLevel = 12000000
#
# Respawn.WarningMessage
# Description: This message will be periodically shown (Frequency specified by Respawn.WarningFrequency) to
# all users of the server, once the Respawn.GuidWarnLevel has been passed, and a restart scheduled.
# It's used to warn users that there will be an out of schedule server restart soon.
# Default: "There will be an unscheduled server restart at 03:00 server time. The server will be available again shortly after."
Respawn.WarningMessage = "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."
#
# Respawn.WarningFrequency
# Description: The frequency (in seconds) that the warning message will be sent to users after a quiet time restart is triggered.
# The message will repeat each time this many seconds passed until the server is restarted.
# If set to 0, no warnings will be sent.
# Default: 1800 - (30 minutes)
Respawn.WarningFrequency = 1800
#
# Respawn.GuidAlertLevel
# Description: The point at which the highest guid for creatures or gameobjects in any map must reach
# before the alert logic is enabled. A restart will then be triggered for 30 mins from that
# point. The maximum guid per map is 16,777,216. So, it must be less than this value.
# Default: 16000000 - 16 million
Respawn.GuidAlertLevel = 16000000
#
# Respawn.AlertRestartReason
# Description: The shutdown reason given when the alert level is reached. The server will use a fixed time of
# 5 minutes and the reason for shutdown will be this message
# Default: "Urgent Maintenance"
Respawn.AlertRestartReason = "Urgent Maintenance"
#
# Respawn.RestartQuietTime
# Description: The hour at which the server will be restarted after the Respawn.GuidWarnLevel
# threshold has been reached. This can be between 0 and 23. 20 will be 8pm server time
# Default: 3 - 3am
Respawn.RestartQuietTime = 3
#
# Respawn.DynamicMode
# Description: Select which mode (if any) should be used to adjust respawn of creatures.
# This will only affect creatures that have dynamic spawn rate scaling enabled in
# the spawn group table (by default, gathering nodes and quest targets with respawn time <30min
# 1 - Use number of players in zone
# Default: 0 - No dynamic respawn function
Respawn.DynamicMode = 0
#
# Respawn.DynamicEscortNPC
# Description: This switch controls the dynamic respawn system for escort NPCs not in instancable maps (base maps only).
# This will cause the respawn timer to begin when an escort event begins, and potentially
# allow multiple instances of the NPC to be alive at the same time (when combined with Respawn.DynamicMode > 0)
# 1 - Enabled
# Default: 0 - Disabled
Respawn.DynamicEscortNPC = 1
#
# 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.
# 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: 10
Respawn.DynamicRateCreature = 10
#
# 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.
# 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: 10
Respawn.DynamicRateGameObject = 10
#
# Respawn.DynamicMinimumGameObject
# Description: The minimum respawn time for a gameobject under dynamic scaling.
# Default: 10 - (10 seconds)
Respawn.DynamicMinimumGameObject = 10
#
###################################################################################################
###################################################################################################
# CHAT SETTINGS
#
@@ -0,0 +1,214 @@
-- Create databases for spawn group template, and spawn group membership
-- Current flags
-- 0x01 Legacy Spawn Mode (spawn using legacy spawn system)
-- 0x02 Manual Spawn (don't automatically spawn, instead spawned by core as part of script)
DROP TABLE IF EXISTS `spawn_group_template`;
CREATE TABLE `spawn_group_template` (
`groupId` int(10) unsigned NOT NULL,
`groupName` varchar(100) NOT NULL,
`groupFlags` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`groupId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `spawn_group`;
CREATE TABLE `spawn_group` (
`groupId` int(10) unsigned NOT NULL,
`spawnType` tinyint(10) unsigned NOT NULL,
`spawnId` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`groupId`,`spawnType`,`spawnId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- Create the default groups
INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES
(0, 'Default Group', 0x01),
(1, 'Legacy Group', (0x01|0x02)),
(2, 'Dynamic Scaling (Quest objectives)', (0x01|0x08)),
(3, 'Dynamic Scaling (Escort NPCs)', (0x01|0x08|0x10)),
(4, 'Dynamic Scaling (Gathering nodes)', (0x01|0x08));
-- Create creature dynamic spawns group (creatures with quest items, or subjects of quests with less than 30min spawn time)
DROP TABLE IF EXISTS `creature_temp_group`;
CREATE TEMPORARY TABLE `creature_temp_group`
(
`creatureId` int(10) unsigned NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `creature_temp_group`
SELECT `guid`
FROM `creature` C
INNER JOIN `creature_questitem` ON `CreatureEntry` = C.`id`
WHERE `spawntimesecs` < 1800
AND `map` IN (0, 1, 530, 571);
INSERT INTO `creature_temp_group`
SELECT `guid`
FROM `creature` C
INNER JOIN `quest_objectives` QO ON QO.`ObjectID` = C.`id` AND QO.`Type` = 0
WHERE `spawntimesecs` < 1800
AND `map` IN (0, 1, 530, 571);
INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`)
SELECT DISTINCT 2, 0, `creatureId`
FROM `creature_temp_group`;
DROP TABLE `creature_temp_group`;
-- Create gameobject dynamic spawns group (gameobjects with quest items, or subjects of quests with less than 30min spawn time)
DROP TABLE IF EXISTS `gameobject_temp_group`;
CREATE TEMPORARY TABLE `gameobject_temp_group`
(
`gameobjectId` int(10) unsigned NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `gameobject_temp_group_ids`;
CREATE TEMPORARY TABLE `gameobject_temp_group_ids`
(
`entryid` int(10) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ALTER TABLE `gameobject_temp_group_ids` ADD INDEX (`entryid`);
INSERT INTO `gameobject_temp_group`
SELECT `guid`
FROM `gameobject` G
INNER JOIN `gameobject_questitem` ON `GameObjectEntry` = G.`id`
WHERE `spawntimesecs` < 1800
AND `map` IN (0, 1, 530, 571);
INSERT INTO `gameobject_temp_group_ids` (`entryid`)
SELECT DISTINCT `ObjectID`
FROM `quest_objectives`
WHERE `Type`=2;
INSERT INTO `gameobject_temp_group`
SELECT `guid`
FROM `gameobject` G
INNER JOIN `gameobject_temp_group_ids` ON `entryid` = G.`id`
WHERE `spawntimesecs` < 1800
AND `map` IN (0, 1, 530, 571);
INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`)
SELECT DISTINCT 2, 1, `gameobjectId`
FROM `gameobject_temp_group`;
DROP TABLE `gameobject_temp_group`;
ALTER TABLE `gameobject_temp_group_ids` DROP INDEX `entryid`;
DROP TABLE `gameobject_temp_group_ids`;
-- Add mining nodes/herb nodes to profession node group
INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`)
SELECT 4, 1, `guid`
FROM `gameobject` g
INNER JOIN `gameobject_template` gt
ON gt.`entry` = g.`id`
WHERE `type` IN (3, 50)
AND `Data0` IN (2, 8, 9, 10, 11, 18, 19, 20, 21, 22, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 47, 48, 49, 50, 51, 379, 380, 399, 400, 439, 440, 441, 442, 443, 444, 519, 521, 719, 939, 1119, 1120,
1121, 1122, 1123, 1124, 1632, 1639, 1641, 1642, 1643, 1644, 1645, 1646, 1649, 1650, 1651, 1652, 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1800, 1860);
-- Add Escort NPCs
INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES
(3, 0, 10873),
(3, 0, 17874),
(3, 0, 40210),
(3, 0, 11348),
(3, 0, 93301),
(3, 0, 93194),
(3, 0, 19107),
(3, 0, 21692),
(3, 0, 21584),
(3, 0, 23229),
(3, 0, 24268),
(3, 0, 21594),
(3, 0, 14387),
(3, 0, 50381),
(3, 0, 15031),
(3, 0, 26987),
(3, 0, 29241),
(3, 0, 32333),
(3, 0, 33115),
(3, 0, 37085),
(3, 0, 41759),
(3, 0, 84459),
(3, 0, 78685),
(3, 0, 62090),
(3, 0, 72388),
(3, 0, 86832),
(3, 0, 67040),
(3, 0, 78781),
(3, 0, 65108),
(3, 0, 63688),
(3, 0, 59383),
(3, 0, 63625),
(3, 0, 70021),
(3, 0, 82071),
(3, 0, 117903),
(3, 0, 111075),
(3, 0, 101136),
(3, 0, 101303),
(3, 0, 122686),
(3, 0, 117065),
(3, 0, 202337),
(3, 0, 2017),
(3, 0, 132683);
-- remove potential duplicates
DELETE FROM `spawn_group` WHERE `groupId` != 3 AND `spawnType`=0 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=3 AND `spawnType`=0) as `temp`);
DELETE FROM `spawn_group` WHERE `groupId` != 4 AND `spawnType`=1 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=4 AND `spawnType`=1) as `temp`);
-- Update trinity strings for various cs_list strings, to support showing spawn ID and guid.
UPDATE `trinity_string`
SET `content_default` = '%s (Entry: %d) - |cffffffff|Hgameobject:%s|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s'
WHERE `entry` = 517;
UPDATE `trinity_string`
SET `content_default` = '%s - |cffffffff|Hcreature:%s|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s'
WHERE `entry` = 515;
UPDATE `trinity_string`
SET `content_default` = '%s - %s X:%f Y:%f Z:%f MapId:%d %s %s'
WHERE `entry` = 1111;
UPDATE `trinity_string`
SET `content_default` = '%s - %s X:%f Y:%f Z:%f MapId:%d %s %s'
WHERE `entry` = 1110;
UPDATE `trinity_string`
SET `entry`=5084
WHERE `entry`=5070;
UPDATE `trinity_string`
SET `entry`=5085
WHERE `entry`=5071;
UPDATE `trinity_string`
SET `entry`=5086
WHERE `entry`=5072;
-- Add new trinity strings for extra npc/gobject info lines
DELETE FROM `trinity_string` WHERE `entry` BETWEEN 5070 AND 5082;
INSERT INTO `trinity_string` (`entry`, `content_default`) VALUES
(5070, 'Spawn group: %s (ID: %u, Flags: %u, Active: %u)'),
(5071, 'Compatibility Mode: %u'),
(5072, 'GUID: %s'),
(5073, 'SpawnID: %s, location (%f, %f, %f)'),
(5074, 'Distance from player %f'),
(5075, 'Spawn group %u not found'),
(5076, 'Spawned a total of %zu objects:'),
(5077, 'Listing %s respawns within %uyd'),
(5078, 'Listing %s respawns for %s (zone %u)'),
(5079, 'SpawnID | Entry | GridXY| Zone | Respawn time (Full)'),
(5080, 'overdue'),
(5081, 'creatures'),
(5082, 'gameobjects');
-- Add new NPC/Gameobject commands
DELETE FROM `command` WHERE `name` IN ('npc spawngroup', 'npc despawngroup', 'gobject spawngroup', 'gobject despawngroup', 'list respawns');
INSERT INTO `command` (`name`, `permission`, `help`) VALUES
('npc spawngroup', 856, 'Syntax: .npc spawngroup $groupId [ignorerespawn] [force]'),
('npc despawngroup', 857, 'Syntax: .npc despawngroup $groupId [removerespawntime]'),
('gobject spawngroup', 858, 'Syntax: .gobject spawngroup $groupId [ignorerespawn] [force]'),
('gobject despawngroup', 859, 'Syntax: .gobject despawngroup $groupId [removerespawntime]'),
('list respawns', 860, 'Syntax: .list respawns [distance]
Lists all pending respawns within <distance> yards, or within current zone if not specified.');