diff --git a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs index ff0616b8e..130e21c24 100644 --- a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs @@ -12,972 +12,1050 @@ using CPA = Server.CommandPropertyAttribute; using static Server.Attributes; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public abstract class BaseSpawner : Item, ISpawner { - public abstract class BaseSpawner : Item, ISpawner + private static WarnTimer m_WarnTimer; + + private Guid _guid; + private int m_Count; + private bool m_Group; + private int m_HomeRange; + private TimeSpan m_MaxDelay; + private TimeSpan m_MinDelay; + private bool m_Running; + private int m_Team; + + private InternalTimer m_Timer; + private int m_WalkingRange = -1; + + public BaseSpawner() : this(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4) { - private static WarnTimer m_WarnTimer; + } - private Guid _guid; - private int m_Count; - private bool m_Group; - private int m_HomeRange; - private TimeSpan m_MaxDelay; - private TimeSpan m_MinDelay; - private bool m_Running; - private int m_Team; + public BaseSpawner(string spawnedName) : this( + 1, + TimeSpan.FromMinutes(5), + TimeSpan.FromMinutes(10), + 0, + 4, + spawnedName + ) + { + } - private InternalTimer m_Timer; - private int m_WalkingRange = -1; + public BaseSpawner( + int amount, int minDelay, int maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : this( + amount, + TimeSpan.FromMinutes(minDelay), + TimeSpan.FromMinutes(maxDelay), + team, + homeRange, + spawnedNames + ) + { + } - public BaseSpawner() : this(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4) + public BaseSpawner( + int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : base(0x1f13) + { + _guid = Guid.NewGuid(); + InitSpawn(amount, minDelay, maxDelay, team, homeRange); + for (var i = 0; i < spawnedNames.Length; i++) { + AddEntry(spawnedNames[i], 100, amount, false); } + } - public BaseSpawner(string spawnedName) : this( - 1, - TimeSpan.FromMinutes(5), - TimeSpan.FromMinutes(10), - 0, - 4, - spawnedName - ) - { - } - - public BaseSpawner( - int amount, int minDelay, int maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : this( - amount, - TimeSpan.FromMinutes(minDelay), - TimeSpan.FromMinutes(maxDelay), - team, - homeRange, - spawnedNames - ) - { - } - - public BaseSpawner( - int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : base(0x1f13) + public BaseSpawner(DynamicJson json, JsonSerializerOptions options) : base(0x1f13) + { + if (!json.GetProperty("guid", options, out _guid)) { _guid = Guid.NewGuid(); - InitSpawn(amount, minDelay, maxDelay, team, homeRange); - for (var i = 0; i < spawnedNames.Length; i++) - { - AddEntry(spawnedNames[i], 100, amount, false); - } } - public BaseSpawner(DynamicJson json, JsonSerializerOptions options) : base(0x1f13) + if (json.GetProperty("name", options, out string name)) { - if (!json.GetProperty("guid", options, out _guid)) - { - _guid = Guid.NewGuid(); - } - - if (json.GetProperty("name", options, out string name)) - { - Name = name; - } - - json.GetProperty("count", options, out int amount); - json.GetProperty("minDelay", options, out TimeSpan minDelay); - json.GetProperty("maxDelay", options, out TimeSpan maxDelay); - json.GetProperty("team", options, out int team); - json.GetProperty("homeRange", options, out int homeRange); - json.GetProperty("walkingRange", options, out int walkingRange); - m_WalkingRange = walkingRange; - - InitSpawn(amount, minDelay, maxDelay, team, homeRange); - - json.GetProperty("entries", options, out List entries); - - foreach (var entry in entries) - { - AddEntry(entry.SpawnedName, entry.SpawnedProbability, entry.SpawnedMaxCount, false); - } + Name = name; } - public BaseSpawner(Serial serial) : base(serial) + json.GetProperty("count", options, out int amount); + json.GetProperty("minDelay", options, out TimeSpan minDelay); + json.GetProperty("maxDelay", options, out TimeSpan maxDelay); + json.GetProperty("team", options, out int team); + json.GetProperty("homeRange", options, out int homeRange); + json.GetProperty("walkingRange", options, out int walkingRange); + m_WalkingRange = walkingRange; + + InitSpawn(amount, minDelay, maxDelay, team, homeRange); + + json.GetProperty("entries", options, out List entries); + + foreach (var entry in entries) { + AddEntry(entry.SpawnedName, entry.SpawnedProbability, entry.SpawnedMaxCount, false); } + } - public override string DefaultName => "Spawner"; - public bool IsFull => Spawned?.Count >= m_Count; - public bool IsEmpty => Spawned?.Count == 0; - public DateTime End { get; set; } + public BaseSpawner(Serial serial) : base(serial) + { + } - public List Entries { get; private set; } + public override string DefaultName => "Spawner"; + public bool IsFull => Spawned?.Count >= m_Count; + public bool IsEmpty => Spawned?.Count == 0; + public DateTime End { get; set; } - public Dictionary Spawned { get; private set; } + public List Entries { get; private set; } - [CommandProperty(AccessLevel.Developer)] - public Guid Guid + public Dictionary Spawned { get; private set; } + + [CommandProperty(AccessLevel.Developer)] + public Guid Guid + { + get => _guid; + set => _guid = value; + } + + [CommandProperty(AccessLevel.Developer)] + public int Count + { + get => m_Count; + set { - get => _guid; - set => _guid = value; - } + m_Count = value; - [CommandProperty(AccessLevel.Developer)] - public int Count - { - get => m_Count; - set + if (IsFull) { - m_Count = value; - - if (IsFull) - { - m_Timer?.Stop(); - } - else if (m_Timer?.Running != true) - { - DoTimer(); - } - - InvalidateProperties(); + m_Timer?.Stop(); } - } - - [CommandProperty(AccessLevel.Developer)] - public virtual WayPoint WayPoint { get; set; } - - [CommandProperty(AccessLevel.Developer)] - public bool Running - { - get => m_Running; - set - { - if (value) - { - Start(); - } - else - { - Stop(); - } - - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public int WalkingRange - { - get => m_WalkingRange; - set - { - m_WalkingRange = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public int Team - { - get => m_Team; - set - { - m_Team = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public TimeSpan MinDelay - { - get => m_MinDelay; - set - { - m_MinDelay = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public TimeSpan MaxDelay - { - get => m_MaxDelay; - set - { - m_MaxDelay = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public TimeSpan NextSpawn - { - get => m_Running && m_Timer?.Running == true ? End - Core.Now : TimeSpan.Zero; - set - { - Start(); - DoTimer(value); - } - } - - [CommandProperty(AccessLevel.Developer)] - public bool Group - { - get => m_Group; - set - { - m_Group = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Developer)] - public bool ReturnOnDeactivate { get; set; } - - public virtual Point3D HomeLocation => Location; - public bool UnlinkOnTaming => true; - - [CommandProperty(AccessLevel.Developer)] - public int HomeRange - { - get => m_HomeRange; - set - { - m_HomeRange = value; - InvalidateProperties(); - } - } - - Region ISpawner.Region => Region.Find(Location, Map); - - public void Remove(ISpawnable spawn) - { - Defrag(); - - if (spawn != null) - { - Spawned.TryGetValue(spawn, out var entry); - - entry?.Spawned.Remove(spawn); - - Spawned.Remove(spawn); - } - - if (m_Running && !IsFull && m_Timer?.Running == false) - { - DoTimer(); - } - } - - public virtual void Respawn() - { - RemoveSpawns(); - - for (var i = 0; i < m_Count; i++) - { - Spawn(); - } - - DoTimer(); // Turn off the timer! - } - - public virtual void ToJson(DynamicJson json, JsonSerializerOptions options) - { - json.Type = GetType().Name; - json.SetProperty("name", options, Name); - json.SetProperty("guid", options, _guid); - json.SetProperty("location", options, Location); - json.SetProperty("map", options, Map); - json.SetProperty("count", options, Count); - json.SetProperty("minDelay", options, MinDelay); - json.SetProperty("maxDelay", options, MaxDelay); - json.SetProperty("team", options, Team); - json.SetProperty("homeRange", options, HomeRange); - json.SetProperty("walkingRange", options, WalkingRange); - json.SetProperty("entries", options, Entries); - } - - public abstract Point3D GetSpawnPosition(ISpawnable spawned, Map map); - - public override void OnAfterDuped(Item newItem) - { - if (newItem is BaseSpawner newSpawner) - { - for (var i = 0; i < Entries.Count; i++) - { - newSpawner.AddEntry( - Entries[i].SpawnedName, - Entries[i].SpawnedProbability, - Entries[i].SpawnedMaxCount, - false - ); - } - } - } - - public SpawnerEntry AddEntry(string creaturename, int probability = 100, int amount = 1, bool dotimer = true) - { - var entry = new SpawnerEntry(creaturename, probability, amount); - Entries.Add(entry); - if (dotimer) - { - DoTimer(TimeSpan.FromSeconds(1)); - } - - return entry; - } - - public void InitSpawn(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange) - { - Visible = false; - Movable = false; - m_Running = true; - m_Group = false; - m_MinDelay = minDelay; - m_MaxDelay = maxDelay; - m_Count = amount; - m_Team = team; - m_HomeRange = homeRange; - Entries = new List(); - Spawned = new Dictionary(); - - DoTimer(TimeSpan.FromSeconds(1)); - } - - public override bool CanSeeStaffOnly(Mobile from) => from.AccessLevel >= AccessLevel.Developer; - - public override bool IsAccessibleTo(Mobile from) => from.AccessLevel >= AccessLevel.Developer; - - public override void OnDoubleClick(Mobile from) - { - from.SendGump(new SpawnerGump(this)); - } - - public virtual void GetSpawnerProperties(IPropertyList list) - { - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (m_Running) - { - list.Add(1060742); // active - - list.Add(1060656, m_Count); // amount to make: ~1_val~ - list.Add(1061169, m_HomeRange); // range ~1_val~ - list.Add(1050039, $"{"walking range:"}\t{m_WalkingRange}"); // ~1_NUMBER~ ~2_ITEMNAME~ - list.Add(1053099, $"{"group:"}\t{m_Group}"); // ~1_oretype~: ~2_armortype~ - list.Add(1060847, $"{"team:"}\t{m_Team}"); // ~1_val~ ~2_val~ - list.Add(1063483, $"{"delay:"}\t{m_MinDelay} to {m_MaxDelay}"); // ~1_MATERIAL~: ~2_ITEMNAME~ - - GetSpawnerProperties(list); - - for (var i = 0; i < 6 && i < Entries.Count; ++i) - { - var entry = Entries[i]; - list.Add(1060658 + i, $"\t{entry.SpawnedName}\t{CountSpawns(entry)}"); - } - } - else - { - list.Add(1060743); // inactive - } - } - - public override void OnSingleClick(Mobile from) - { - base.OnSingleClick(from); - - if (m_Running) - { - LabelTo(from, "[Running]"); - } - else - { - LabelTo(from, "[Off]"); - } - } - - public void Start() - { - if (!m_Running) - { - if (Entries.Count > 0) - { - m_Running = true; - DoTimer(); - } - } - } - - public void Stop() - { - m_Timer?.Stop(); - m_Running = false; - } - - public void Defrag() - { - Entries ??= new List(); - - for (var i = 0; i < Entries.Count; ++i) - { - Entries[i].Defrag(this); - } - } - - public virtual bool OnDefragSpawn(ISpawnable spawned, bool remove) - { - if (!remove) // Override could have set it to true already - { - remove = spawned.Deleted || spawned.Spawner == null || spawned switch - { - Item item => item.RootParent is Mobile || item.IsLockedDown || item.IsSecure, - Mobile m => m is BaseCreature c && (c.Controlled || c.IsStabled), - _ => true - }; - } - - if (remove) - { - Spawned.Remove(spawned); - } - - return remove; - } - - public void OnTick() - { - if (m_Group) - { - Defrag(); - - if (Spawned.Count > 0) - { - return; - } - - Respawn(); - } - else - { - Spawn(); - } - - DoTimer(); - } - - public virtual void Spawn() - { - Defrag(); - - if (Entries.Count <= 0 || IsFull) - { - return; - } - - var probsum = 0; - - foreach (var spawnerEntry in Entries) - { - if (!spawnerEntry.IsFull) - { - probsum += spawnerEntry.SpawnedProbability; - } - } - - if (probsum <= 0) - { - return; - } - - var rand = Utility.RandomMinMax(1, probsum); - - for (var i = 0; i < Entries.Count; i++) - { - var entry = Entries[i]; - if (entry.IsFull) - { - continue; - } - - if (rand <= entry.SpawnedProbability) - { - Spawn(entry, out var flags); - entry.Valid = flags; - return; - } - - rand -= entry.SpawnedProbability; - } - } - - private static string[,] FormatProperties(string[] args) - { - string[,] props; - - var remains = args.Length; - - if (remains >= 2) - { - props = new string[remains / 2, 2]; - - remains /= 2; - - for (var j = 0; j < remains; ++j) - { - props[j, 0] = args[j * 2]; - props[j, 1] = args[j * 2 + 1]; - } - } - else - { - props = new string[0, 0]; - } - - return props; - } - - private static PropertyInfo[] GetTypeProperties(Type type, string[,] props) - { - PropertyInfo[] realProps = null; - - if (props != null) - { - realProps = new PropertyInfo[props.GetLength(0)]; - - var allProps = - type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); - - for (var i = 0; i < realProps.Length; ++i) - { - PropertyInfo thisProp = null; - - var propName = props[i, 0]; - - for (var j = 0; thisProp == null && j < allProps.Length; ++j) - { - if (propName.InsensitiveEquals(allProps[j].Name)) - { - thisProp = allProps[j]; - } - } - - if (thisProp == null) - { - return null; - } - - var attr = GetCPA(thisProp); - - if (attr == null || attr.WriteLevel > AccessLevel.Developer || !thisProp.CanWrite || attr.ReadOnly) - { - return null; - } - - realProps[i] = thisProp; - } - } - - return realProps; - } - - public bool Spawn(int index, out EntryFlags flags) - { - if (index >= 0 && index < Entries.Count) - { - return Spawn(Entries[index], out flags); - } - - flags = EntryFlags.InvalidEntry; - return false; - } - - public bool Spawn(SpawnerEntry entry, out EntryFlags flags) - { - var map = GetSpawnMap(); - flags = EntryFlags.None; - - if (map == null || map == Map.Internal || Parent != null) - { - return false; - } - - // Defrag taken care of in Spawn(), beforehand - // Count check taken care of in Spawn(), beforehand - - var type = AssemblyHandler.FindTypeByName(entry.SpawnedName); - - if (type == null) - { - flags = EntryFlags.InvalidType; - return false; - } - - try - { - IEntity entity = null; - - var propargs = string.IsNullOrEmpty(entry.Properties) - ? Array.Empty() - : CommandSystem.Split(entry.Properties.Trim()); - - var props = FormatProperties(propargs); - - var realProps = GetTypeProperties(type, props); - - if (realProps == null) - { - flags = EntryFlags.InvalidProps; - return false; - } - - var paramargs = string.IsNullOrEmpty(entry.Parameters) - ? Array.Empty() - : entry.Parameters.Trim().Split(' '); - - if (paramargs.Length == 0) - { - entity = type.CreateInstance( - ci => IsConstructible(ci, AccessLevel.Developer) - ); - } - else - { - var ctors = type.GetConstructors(); - - for (var i = 0; i < ctors.Length; ++i) - { - var ctor = ctors[i]; - - if (IsConstructible(ctor, AccessLevel.Developer)) - { - var paramList = ctor.GetParameters(); - - if (paramargs.Length == paramList.Length) - { - var paramValues = Add.ParseValues(paramList, paramargs); - - if (paramValues != null) - { - entity = ctor.Invoke(paramValues) as IEntity; - break; - } - } - } - } - } - - if (entity == null) - { - flags = EntryFlags.InvalidType | EntryFlags.InvalidParams; - return false; - } - - for (var i = 0; i < realProps.Length; i++) - { - if (realProps[i] != null) - { - var result = Types.TryParse( - realProps[i].PropertyType, - props[i, 1], - out var toSet - ); - - if (result == null) - { - realProps[i].SetValue(entity, toSet, null); - } - else - { - flags = EntryFlags.InvalidProps; - - (entity as ISpawnable)?.Delete(); - - return false; - } - } - } - - if (entity is Mobile m) - { - Spawned.Add(m, entry); - entry.Spawned.Add(m); - - var loc = m is BaseVendor ? Location : GetSpawnPosition(m, map); - - m.OnBeforeSpawn(loc, map); - m.MoveToWorld(loc, map); - - if (m is BaseCreature c) - { - var walkrange = GetWalkingRange(); - - c.RangeHome = walkrange >= 0 ? walkrange : m_HomeRange; - c.CurrentWayPoint = WayPoint; - - if (m_Team > 0) - { - c.Team = m_Team; - } - - c.Home = Location; - c.HomeMap = Map; - } - - m.Spawner = this; - m.OnAfterSpawn(); - } - else if (entity is Item item) - { - Spawned.Add(item, entry); - entry.Spawned.Add(item); - - var loc = GetSpawnPosition(item, map); - - item.OnBeforeSpawn(loc, map); - - item.MoveToWorld(loc, map); - - item.Spawner = this; - item.OnAfterSpawn(); - } - else - { - // Other IEntity types that might get created are simply not supported - flags = EntryFlags.InvalidType | EntryFlags.InvalidParams; - return false; - } - } - catch (Exception e) - { - Console.WriteLine($"EXCEPTION CAUGHT: {Serial}"); - Console.WriteLine(e); - return false; - } - - ClearProperties(); - return true; - } - - public virtual int GetWalkingRange() => m_WalkingRange; - - public virtual Map GetSpawnMap() => Map; - - public void DoTimer() - { - if (!m_Running) - { - return; - } - - var minSeconds = (int)m_MinDelay.TotalSeconds; - var maxSeconds = (int)m_MaxDelay.TotalSeconds; - - var delay = TimeSpan.FromSeconds(Utility.RandomMinMax(minSeconds, maxSeconds)); - DoTimer(delay); - } - - public virtual void DoTimer(TimeSpan delay) - { - if (!m_Running) - { - return; - } - - End = Core.Now + delay; - - if (m_Timer == null) - { - m_Timer = new InternalTimer(this, delay); - } - else - { - m_Timer.Stop(); - m_Timer.Delay = delay; - } - - if (!IsFull) - { - m_Timer.Start(); - } - } - - public int CountSpawns(SpawnerEntry entry) - { - Defrag(); - - return entry.Spawned.Count; - } - - public void RemoveEntry(SpawnerEntry entry) - { - Defrag(); - - for (var i = entry.Spawned.Count - 1; i >= 0; i--) - { - var e = entry.Spawned[i]; - entry.Spawned.RemoveAt(i); - e?.Delete(); - } - - Entries.Remove(entry); - - if (m_Running && !IsFull && m_Timer?.Running == false) + else if (m_Timer?.Running != true) { DoTimer(); } InvalidateProperties(); } + } - public void RemoveSpawn(int index) // Entry + [CommandProperty(AccessLevel.Developer)] + public virtual WayPoint WayPoint { get; set; } + + [CommandProperty(AccessLevel.Developer)] + public bool Running + { + get => m_Running; + set { - if (index >= 0 && index < Entries.Count) + if (value) { - RemoveSpawn(Entries[index]); + Start(); + } + else + { + Stop(); + } + + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public int WalkingRange + { + get => m_WalkingRange; + set + { + m_WalkingRange = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public int Team + { + get => m_Team; + set + { + m_Team = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public TimeSpan MinDelay + { + get => m_MinDelay; + set + { + m_MinDelay = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public TimeSpan MaxDelay + { + get => m_MaxDelay; + set + { + m_MaxDelay = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public TimeSpan NextSpawn + { + get => m_Running && m_Timer?.Running == true ? End - Core.Now : TimeSpan.Zero; + set + { + Start(); + DoTimer(value); + } + } + + [CommandProperty(AccessLevel.Developer)] + public bool Group + { + get => m_Group; + set + { + m_Group = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Developer)] + public bool ReturnOnDeactivate { get; set; } + + public virtual Point3D HomeLocation => Location; + public bool UnlinkOnTaming => true; + + [CommandProperty(AccessLevel.Developer)] + public int HomeRange + { + get => m_HomeRange; + set + { + m_HomeRange = value; + InvalidateProperties(); + } + } + + Region ISpawner.Region => Region.Find(Location, Map); + + public void Remove(ISpawnable spawn) + { + Defrag(); + + if (spawn != null) + { + Spawned.TryGetValue(spawn, out var entry); + + entry?.Spawned.Remove(spawn); + + Spawned.Remove(spawn); + } + + if (m_Running && !IsFull && m_Timer?.Running == false) + { + DoTimer(); + } + } + + public virtual void Respawn() + { + RemoveSpawns(); + + for (var i = 0; i < m_Count; i++) + { + Spawn(); + } + + DoTimer(); // Turn off the timer! + } + + public virtual void Reset() + { + Stop(); + RemoveSpawns(); + } + + public virtual void ToJson(DynamicJson json, JsonSerializerOptions options) + { + json.Type = GetType().Name; + json.SetProperty("name", options, Name); + json.SetProperty("guid", options, _guid); + json.SetProperty("location", options, Location); + json.SetProperty("map", options, Map); + json.SetProperty("count", options, Count); + json.SetProperty("minDelay", options, MinDelay); + json.SetProperty("maxDelay", options, MaxDelay); + json.SetProperty("team", options, Team); + json.SetProperty("homeRange", options, HomeRange); + json.SetProperty("walkingRange", options, WalkingRange); + json.SetProperty("entries", options, Entries); + } + + public abstract Point3D GetSpawnPosition(ISpawnable spawned, Map map); + + public override void OnAfterDuped(Item newItem) + { + if (newItem is BaseSpawner newSpawner) + { + for (var i = 0; i < Entries.Count; i++) + { + newSpawner.AddEntry( + Entries[i].SpawnedName, + Entries[i].SpawnedProbability, + Entries[i].SpawnedMaxCount, + false + ); + } + } + } + + public SpawnerEntry AddEntry(string creaturename, int probability = 100, int amount = 1, bool dotimer = true) + { + var entry = new SpawnerEntry(creaturename, probability, amount); + Entries.Add(entry); + if (dotimer) + { + DoTimer(TimeSpan.FromSeconds(1)); + } + + return entry; + } + + public void InitSpawn(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange) + { + Visible = false; + Movable = false; + m_Running = true; + m_Group = false; + m_MinDelay = minDelay; + m_MaxDelay = maxDelay; + m_Count = amount; + m_Team = team; + m_HomeRange = homeRange; + Entries = new List(); + Spawned = new Dictionary(); + + DoTimer(TimeSpan.FromSeconds(1)); + } + + public override bool CanSeeStaffOnly(Mobile from) => from.AccessLevel >= AccessLevel.Developer; + + public override bool IsAccessibleTo(Mobile from) => from.AccessLevel >= AccessLevel.Developer; + + public override void OnDoubleClick(Mobile from) + { + from.SendGump(new SpawnerGump(this)); + } + + public virtual void GetSpawnerProperties(IPropertyList list) + { + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (m_Running) + { + list.Add(1060742); // active + + list.Add(1060656, m_Count); // amount to make: ~1_val~ + list.Add(1061169, m_HomeRange); // range ~1_val~ + list.Add(1050039, $"{"walking range:"}\t{m_WalkingRange}"); // ~1_NUMBER~ ~2_ITEMNAME~ + list.Add(1053099, $"{"group:"}\t{m_Group}"); // ~1_oretype~: ~2_armortype~ + list.Add(1060847, $"{"team:"}\t{m_Team}"); // ~1_val~ ~2_val~ + list.Add(1063483, $"{"delay:"}\t{m_MinDelay} to {m_MaxDelay}"); // ~1_MATERIAL~: ~2_ITEMNAME~ + + GetSpawnerProperties(list); + + for (var i = 0; i < 6 && i < Entries.Count; ++i) + { + var entry = Entries[i]; + list.Add(1060658 + i, $"\t{entry.SpawnedName}\t{CountSpawns(entry)}"); + } + } + else + { + list.Add(1060743); // inactive + } + } + + public override void OnSingleClick(Mobile from) + { + base.OnSingleClick(from); + + if (m_Running) + { + LabelTo(from, "[Running]"); + } + else + { + LabelTo(from, "[Off]"); + } + } + + public void Start() + { + if (!m_Running) + { + if (Entries.Count > 0) + { + m_Running = true; + DoTimer(); + } + } + } + + public void Stop() + { + m_Timer?.Stop(); + m_Running = false; + } + + public void Defrag() + { + Entries ??= new List(); + + for (var i = 0; i < Entries.Count; ++i) + { + Entries[i].Defrag(this); + } + } + + public virtual bool OnDefragSpawn(ISpawnable spawned, bool remove) + { + if (!remove) // Override could have set it to true already + { + remove = spawned.Deleted || spawned.Spawner == null || spawned switch + { + Item item => item.RootParent is Mobile || item.IsLockedDown || item.IsSecure, + Mobile m => m is BaseCreature c && (c.Controlled || c.IsStabled), + _ => true + }; + } + + if (remove) + { + Spawned.Remove(spawned); + } + + return remove; + } + + public void OnTick() + { + if (m_Group) + { + Defrag(); + + if (Spawned.Count > 0) + { + return; + } + + Respawn(); + } + else + { + Spawn(); + } + + DoTimer(); + } + + public virtual void Spawn() + { + Defrag(); + + if (Entries.Count <= 0 || IsFull) + { + return; + } + + var probsum = 0; + + foreach (var spawnerEntry in Entries) + { + if (!spawnerEntry.IsFull) + { + probsum += spawnerEntry.SpawnedProbability; } } - public void RemoveSpawn(SpawnerEntry entry) + if (probsum <= 0) { - for (var i = entry.Spawned.Count - 1; i >= 0; i--) + return; + } + + var rand = Utility.RandomMinMax(1, probsum); + + for (var i = 0; i < Entries.Count; i++) + { + var entry = Entries[i]; + if (entry.IsFull) { - var e = entry.Spawned[i]; + continue; + } + + if (rand <= entry.SpawnedProbability) + { + Spawn(entry, out var flags); + entry.Valid = flags; + return; + } + + rand -= entry.SpawnedProbability; + } + } + + private static string[,] FormatProperties(string[] args) + { + string[,] props; + + var remains = args.Length; + + if (remains >= 2) + { + props = new string[remains / 2, 2]; + + remains /= 2; + + for (var j = 0; j < remains; ++j) + { + props[j, 0] = args[j * 2]; + props[j, 1] = args[j * 2 + 1]; + } + } + else + { + props = new string[0, 0]; + } + + return props; + } + + private static PropertyInfo[] GetTypeProperties(Type type, string[,] props) + { + PropertyInfo[] realProps = null; + + if (props != null) + { + realProps = new PropertyInfo[props.GetLength(0)]; + + var allProps = + type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); + + for (var i = 0; i < realProps.Length; ++i) + { + PropertyInfo thisProp = null; + + var propName = props[i, 0]; + + for (var j = 0; thisProp == null && j < allProps.Length; ++j) + { + if (propName.InsensitiveEquals(allProps[j].Name)) + { + thisProp = allProps[j]; + } + } + + if (thisProp == null) + { + return null; + } + + var attr = GetCPA(thisProp); + + if (attr == null || attr.WriteLevel > AccessLevel.Developer || !thisProp.CanWrite || attr.ReadOnly) + { + return null; + } + + realProps[i] = thisProp; + } + } + + return realProps; + } + + public bool Spawn(int index, out EntryFlags flags) + { + if (index >= 0 && index < Entries.Count) + { + return Spawn(Entries[index], out flags); + } + + flags = EntryFlags.InvalidEntry; + return false; + } + + public bool Spawn(SpawnerEntry entry, out EntryFlags flags) + { + var map = GetSpawnMap(); + flags = EntryFlags.None; + + if (map == null || map == Map.Internal || Parent != null) + { + return false; + } + + // Defrag taken care of in Spawn(), beforehand + // Count check taken care of in Spawn(), beforehand + + var type = AssemblyHandler.FindTypeByName(entry.SpawnedName); + + if (type == null) + { + flags = EntryFlags.InvalidType; + return false; + } + + try + { + IEntity entity = null; + + var propargs = string.IsNullOrEmpty(entry.Properties) + ? Array.Empty() + : CommandSystem.Split(entry.Properties.Trim()); + + var props = FormatProperties(propargs); + + var realProps = GetTypeProperties(type, props); + + if (realProps == null) + { + flags = EntryFlags.InvalidProps; + return false; + } + + var paramargs = string.IsNullOrEmpty(entry.Parameters) + ? Array.Empty() + : entry.Parameters.Trim().Split(' '); + + if (paramargs.Length == 0) + { + entity = type.CreateInstance( + ci => IsConstructible(ci, AccessLevel.Developer) + ); + } + else + { + var ctors = type.GetConstructors(); + + for (var i = 0; i < ctors.Length; ++i) + { + var ctor = ctors[i]; + + if (IsConstructible(ctor, AccessLevel.Developer)) + { + var paramList = ctor.GetParameters(); + + if (paramargs.Length == paramList.Length) + { + var paramValues = Add.ParseValues(paramList, paramargs); + + if (paramValues != null) + { + entity = ctor.Invoke(paramValues) as IEntity; + break; + } + } + } + } + } + + if (entity == null) + { + flags = EntryFlags.InvalidType | EntryFlags.InvalidParams; + return false; + } + + for (var i = 0; i < realProps.Length; i++) + { + if (realProps[i] != null) + { + var result = Types.TryParse( + realProps[i].PropertyType, + props[i, 1], + out var toSet + ); + + if (result == null) + { + realProps[i].SetValue(entity, toSet, null); + } + else + { + flags = EntryFlags.InvalidProps; + + (entity as ISpawnable)?.Delete(); + + return false; + } + } + } + + if (entity is Mobile m) + { + Spawned.Add(m, entry); + entry.Spawned.Add(m); + + var loc = m is BaseVendor ? Location : GetSpawnPosition(m, map); + + m.OnBeforeSpawn(loc, map); + m.MoveToWorld(loc, map); + + if (m is BaseCreature c) + { + var walkrange = GetWalkingRange(); + + c.RangeHome = walkrange >= 0 ? walkrange : m_HomeRange; + c.CurrentWayPoint = WayPoint; + + if (m_Team > 0) + { + c.Team = m_Team; + } + + c.Home = Location; + c.HomeMap = Map; + } + + m.Spawner = this; + m.OnAfterSpawn(); + } + else if (entity is Item item) + { + Spawned.Add(item, entry); + entry.Spawned.Add(item); + + var loc = GetSpawnPosition(item, map); + + item.OnBeforeSpawn(loc, map); + + item.MoveToWorld(loc, map); + + item.Spawner = this; + item.OnAfterSpawn(); + } + else + { + // Other IEntity types that might get created are simply not supported + flags = EntryFlags.InvalidType | EntryFlags.InvalidParams; + return false; + } + } + catch (Exception e) + { + Console.WriteLine($"EXCEPTION CAUGHT: {Serial}"); + Console.WriteLine(e); + return false; + } + + ClearProperties(); + return true; + } + + public virtual int GetWalkingRange() => m_WalkingRange; + + public virtual Map GetSpawnMap() => Map; + + public void DoTimer() + { + if (!m_Running) + { + return; + } + + var minSeconds = (int)m_MinDelay.TotalSeconds; + var maxSeconds = (int)m_MaxDelay.TotalSeconds; + + var delay = TimeSpan.FromSeconds(Utility.RandomMinMax(minSeconds, maxSeconds)); + DoTimer(delay); + } + + public virtual void DoTimer(TimeSpan delay) + { + if (!m_Running) + { + return; + } + + End = Core.Now + delay; + + if (m_Timer == null) + { + m_Timer = new InternalTimer(this, delay); + } + else + { + m_Timer.Stop(); + m_Timer.Delay = delay; + } + + if (!IsFull) + { + m_Timer.Start(); + } + } + + public int CountSpawns(SpawnerEntry entry) + { + Defrag(); + + return entry.Spawned.Count; + } + + public void RemoveEntry(SpawnerEntry entry) + { + Defrag(); + + for (var i = entry.Spawned.Count - 1; i >= 0; i--) + { + var e = entry.Spawned[i]; + entry.Spawned.RemoveAt(i); + e?.Delete(); + } + + Entries.Remove(entry); + + if (m_Running && !IsFull && m_Timer?.Running == false) + { + DoTimer(); + } + + InvalidateProperties(); + } + + public void RemoveSpawn(int index) // Entry + { + if (index >= 0 && index < Entries.Count) + { + RemoveSpawn(Entries[index]); + } + } + + public void RemoveSpawn(SpawnerEntry entry) + { + for (var i = entry.Spawned.Count - 1; i >= 0; i--) + { + var e = entry.Spawned[i]; + + if (e != null) + { + entry.Spawned.RemoveAt(i); + Spawned.Remove(e); + e.Delete(); + } + } + } + + public void RemoveSpawns() + { + Defrag(); + + for (var i = 0; i < Entries.Count; i++) + { + var entry = Entries[i]; + + for (var j = entry.Spawned.Count - 1; j >= 0; j--) + { + var e = entry.Spawned[j]; if (e != null) { - entry.Spawned.RemoveAt(i); Spawned.Remove(e); + entry.Spawned.RemoveAt(j); e.Delete(); } } } - public void RemoveSpawns() + if (m_Running && !IsFull && m_Timer?.Running == false) { - Defrag(); + DoTimer(); + } - for (var i = 0; i < Entries.Count; i++) - { - var entry = Entries[i]; + InvalidateProperties(); + } - for (var j = entry.Spawned.Count - 1; j >= 0; j--) + public void BringToHome() + { + Defrag(); + + foreach (var e in Spawned.Keys) + { + e?.MoveToWorld(Location, Map); + } + } + + public override void OnDelete() + { + base.OnDelete(); + + Stop(); + RemoveSpawns(); + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(9); // version + + writer.Write(_guid); + + writer.Write(ReturnOnDeactivate); + + writer.Write(Entries.Count); + + for (var i = 0; i < Entries.Count; ++i) + { + Entries[i].Serialize(writer); + } + + writer.Write(m_WalkingRange); + + writer.Write(WayPoint); + + writer.Write(m_Group); + + writer.Write(m_MinDelay); + writer.Write(m_MaxDelay); + writer.Write(m_Count); + writer.Write(m_Team); + writer.Write(m_HomeRange); + writer.Write(m_Running); + + if (m_Running) + { + writer.WriteDeltaTime(End); + } + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + Spawned = new Dictionary(); + + if (version < 7) + { + Entries = new List(); + } + + switch (version) + { + case 9: { - var e = entry.Spawned[j]; - - if (e != null) - { - Spawned.Remove(e); - entry.Spawned.RemoveAt(j); - e.Delete(); - } + _guid = reader.ReadGuid(); + goto case 8; } - } + case 8: + { + ReturnOnDeactivate = reader.ReadBool(); + goto case 7; + } + case 7: + { + var size = reader.ReadInt(); - if (m_Running && !IsFull && m_Timer?.Running == false) - { - DoTimer(); - } + Entries = new List(size); - InvalidateProperties(); - } - - public void BringToHome() - { - Defrag(); - - foreach (var e in Spawned.Keys) - { - e?.MoveToWorld(Location, Map); - } - } - - public override void OnDelete() - { - base.OnDelete(); - - Stop(); - RemoveSpawns(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(9); // version - - writer.Write(_guid); - - writer.Write(ReturnOnDeactivate); - - writer.Write(Entries.Count); - - for (var i = 0; i < Entries.Count; ++i) - { - Entries[i].Serialize(writer); - } - - writer.Write(m_WalkingRange); - - writer.Write(WayPoint); - - writer.Write(m_Group); - - writer.Write(m_MinDelay); - writer.Write(m_MaxDelay); - writer.Write(m_Count); - writer.Write(m_Team); - writer.Write(m_HomeRange); - writer.Write(m_Running); - - if (m_Running) - { - writer.WriteDeltaTime(End); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - Spawned = new Dictionary(); - - if (version < 7) - { - Entries = new List(); - } - - switch (version) - { - case 9: + for (var i = 0; i < size; ++i) { - _guid = reader.ReadGuid(); - goto case 8; + Entries.Add(new SpawnerEntry(this, reader)); } - case 8: - { - ReturnOnDeactivate = reader.ReadBool(); - goto case 7; - } - case 7: - { - var size = reader.ReadInt(); - Entries = new List(size); + goto case 4; // Skip the other crap + } + case 6: + { + var size = reader.ReadInt(); - for (var i = 0; i < size; ++i) + var addentries = Entries.Count == 0; + + for (var i = 0; i < size; ++i) + { + if (addentries) { - Entries.Add(new SpawnerEntry(this, reader)); + Entries.Add(new SpawnerEntry(string.Empty, 100, reader.ReadInt())); + } + else + { + Entries[i].SpawnedMaxCount = reader.ReadInt(); } - - goto case 4; // Skip the other crap } - case 6: + + goto case 5; + } + case 5: + { + var size = reader.ReadInt(); + + var addentries = Entries.Count == 0; + + for (var i = 0; i < size; ++i) + { + if (addentries) + { + Entries.Add(new SpawnerEntry(string.Empty, reader.ReadInt(), 1)); + } + else + { + Entries[i].SpawnedProbability = reader.ReadInt(); + } + } + + goto case 4; + } + case 4: + { + m_WalkingRange = reader.ReadInt(); + + goto case 3; + } + case 3: + case 2: + { + WayPoint = reader.ReadEntity(); + + goto case 1; + } + + case 1: + { + m_Group = reader.ReadBool(); + + goto case 0; + } + + case 0: + { + m_MinDelay = reader.ReadTimeSpan(); + m_MaxDelay = reader.ReadTimeSpan(); + m_Count = reader.ReadInt(); + m_Team = reader.ReadInt(); + m_HomeRange = reader.ReadInt(); + m_Running = reader.ReadBool(); + + var ts = m_Running ? reader.ReadDeltaTime() - Core.Now : TimeSpan.Zero; + + if (version < 7) { var size = reader.ReadInt(); @@ -985,224 +1063,151 @@ namespace Server.Engines.Spawners for (var i = 0; i < size; ++i) { + var typeName = reader.ReadString(); + if (addentries) { - Entries.Add(new SpawnerEntry(string.Empty, 100, reader.ReadInt())); + Entries.Add(new SpawnerEntry(typeName, 100, 1)); } else { - Entries[i].SpawnedMaxCount = reader.ReadInt(); + Entries[i].SpawnedName = typeName; + } + + if (AssemblyHandler.FindTypeByName(typeName) == null) + { + m_WarnTimer ??= new WarnTimer(); + + m_WarnTimer.Add(Location, Map, typeName); } } - goto case 5; - } - case 5: - { - var size = reader.ReadInt(); + var count = reader.ReadInt(); - var addentries = Entries.Count == 0; - - for (var i = 0; i < size; ++i) + for (var i = 0; i < count; ++i) { - if (addentries) + var e = reader.ReadEntity(); + + if (e == null) { - Entries.Add(new SpawnerEntry(string.Empty, reader.ReadInt(), 1)); - } - else - { - Entries[i].SpawnedProbability = reader.ReadInt(); - } - } - - goto case 4; - } - case 4: - { - m_WalkingRange = reader.ReadInt(); - - goto case 3; - } - case 3: - case 2: - { - WayPoint = reader.ReadEntity(); - - goto case 1; - } - - case 1: - { - m_Group = reader.ReadBool(); - - goto case 0; - } - - case 0: - { - m_MinDelay = reader.ReadTimeSpan(); - m_MaxDelay = reader.ReadTimeSpan(); - m_Count = reader.ReadInt(); - m_Team = reader.ReadInt(); - m_HomeRange = reader.ReadInt(); - m_Running = reader.ReadBool(); - - var ts = m_Running ? reader.ReadDeltaTime() - Core.Now : TimeSpan.Zero; - - if (version < 7) - { - var size = reader.ReadInt(); - - var addentries = Entries.Count == 0; - - for (var i = 0; i < size; ++i) - { - var typeName = reader.ReadString(); - - if (addentries) - { - Entries.Add(new SpawnerEntry(typeName, 100, 1)); - } - else - { - Entries[i].SpawnedName = typeName; - } - - if (AssemblyHandler.FindTypeByName(typeName) == null) - { - m_WarnTimer ??= new WarnTimer(); - - m_WarnTimer.Add(Location, Map, typeName); - } + continue; } - var count = reader.ReadInt(); - - for (var i = 0; i < count; ++i) + if (e is BaseCreature creature) { - var e = reader.ReadEntity(); + creature.RemoveIfUntamed = true; + } - if (e == null) + e.Spawner = this; + + for (var j = 0; j < Entries.Count; j++) + { + if (AssemblyHandler.FindTypeByName(Entries[j].SpawnedName) == e.GetType()) { - continue; - } - - if (e is BaseCreature creature) - { - creature.RemoveIfUntamed = true; - } - - e.Spawner = this; - - for (var j = 0; j < Entries.Count; j++) - { - if (AssemblyHandler.FindTypeByName(Entries[j].SpawnedName) == e.GetType()) - { - Entries[j].Spawned.Add(e); - Spawned.Add(e, Entries[j]); - break; - } + Entries[j].Spawned.Add(e); + Spawned.Add(e, Entries[j]); + break; } } } - - DoTimer(ts); - - break; } - } - if (version < 4) - { - m_WalkingRange = m_HomeRange; - } + DoTimer(ts); + + break; + } } - private class InternalTimer : Timer + if (version < 4) { - private readonly BaseSpawner m_Spawner; - - public InternalTimer(BaseSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner; - - protected override void OnTick() - { - if (m_Spawner?.Deleted == false) - { - m_Spawner.OnTick(); - } - } + m_WalkingRange = m_HomeRange; } + } - private class WarnTimer : Timer + private class InternalTimer : Timer + { + private readonly BaseSpawner m_Spawner; + + public InternalTimer(BaseSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner; + + protected override void OnTick() { - private readonly List m_List; - - public WarnTimer() : base(TimeSpan.FromSeconds(1.0)) + if (m_Spawner?.Deleted == false) { - m_List = new List(); - Start(); - } - - public void Add(Point3D p, Map map, string name) - { - m_List.Add(new WarnEntry(p, map, name)); - } - - protected override void OnTick() - { - try - { - Console.WriteLine("Warning: {0} bad spawns detected, logged: 'badspawn.log'", m_List.Count); - - using var op = new StreamWriter("badspawn.log", true); - op.WriteLine("# Bad spawns : {0}", DateTime.Now); - op.WriteLine("# Format: X Y Z F Name"); - op.WriteLine(); - - foreach (var e in m_List) - { - op.WriteLine( - "{0}\t{1}\t{2}\t{3}\t{4}", - e.m_Point.X, - e.m_Point.Y, - e.m_Point.Z, - e.m_Map, - e.m_Name - ); - } - - op.WriteLine(); - op.WriteLine(); - } - catch - { - // ignored - } - } - - private class WarnEntry - { - public readonly Map m_Map; - public readonly string m_Name; - public Point3D m_Point; - - public WarnEntry(Point3D p, Map map, string name) - { - m_Point = p; - m_Map = map; - m_Name = name; - } + m_Spawner.OnTick(); } } } - [Flags] - public enum EntryFlags + private class WarnTimer : Timer { - None = 0x000, - InvalidType = 0x001, - InvalidParams = 0x002, - InvalidProps = 0x004, - InvalidEntry = 0x008 + private readonly List m_List; + + public WarnTimer() : base(TimeSpan.FromSeconds(1.0)) + { + m_List = new List(); + Start(); + } + + public void Add(Point3D p, Map map, string name) + { + m_List.Add(new WarnEntry(p, map, name)); + } + + protected override void OnTick() + { + try + { + Console.WriteLine("Warning: {0} bad spawns detected, logged: 'badspawn.log'", m_List.Count); + + using var op = new StreamWriter("badspawn.log", true); + op.WriteLine("# Bad spawns : {0}", DateTime.Now); + op.WriteLine("# Format: X Y Z F Name"); + op.WriteLine(); + + foreach (var e in m_List) + { + op.WriteLine( + "{0}\t{1}\t{2}\t{3}\t{4}", + e.m_Point.X, + e.m_Point.Y, + e.m_Point.Z, + e.m_Map, + e.m_Name + ); + } + + op.WriteLine(); + op.WriteLine(); + } + catch + { + // ignored + } + } + + private class WarnEntry + { + public readonly Map m_Map; + public readonly string m_Name; + public Point3D m_Point; + + public WarnEntry(Point3D p, Map map, string name) + { + m_Point = p; + m_Map = map; + m_Name = name; + } + } } } + +[Flags] +public enum EntryFlags +{ + None = 0x000, + InvalidType = 0x001, + InvalidParams = 0x002, + InvalidProps = 0x004, + InvalidEntry = 0x008 +} diff --git a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs index 4e24ef087..e3092ebe3 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs @@ -13,81 +13,88 @@ * along with this program. If not, see . * *************************************************************************/ +using System; using System.Collections.Generic; using System.IO; using Server.Commands.Generic; using Server.Json; using Server.Network; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public class ExportSpawnersCommand : BaseCommand { - public class ExportSpawnersCommand : BaseCommand + public static void Initialize() { - public static void Initialize() + TargetCommands.Register(new ExportSpawnersCommand()); + } + + public ExportSpawnersCommand() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.AllItems & ~CommandSupport.Contained; + Commands = new[] { "ExportSpawners" }; + ObjectTypes = ObjectTypes.Items; + Usage = "ExportSpawners"; + Description = "Exports the given spawners to a file"; + ListOptimized = true; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + string path = e.Arguments.Length == 0 ? string.Empty : e.Arguments[0].Trim(); + string condition = e.Arguments.Length == 2 ? e.Arguments[1].Trim() : string.Empty; + + if (string.IsNullOrEmpty(path)) { - TargetCommands.Register(new ExportSpawnersCommand()); + path = Path.Combine(Core.BaseDirectory, $"Data/Spawns/{Utility.GetTimeStamp()}.json"); + } + else + { + var directory = Path.GetDirectoryName(Path.GetFullPath(path!))!; + if (!Path.IsPathRooted(path)) + { + path = Path.Combine(Core.BaseDirectory, path); + PathUtility.EnsureDirectory(directory); + } + else if (!Directory.Exists(directory)) + { + LogFailure("Directory doesn't exist."); + return; + } } - public ExportSpawnersCommand() + NetState.FlushAll(); + + var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); + + var spawnRecords = new List(list.Count); + for (var i = 0; i < list.Count; i++) { - AccessLevel = AccessLevel.GameMaster; - Supports = CommandSupport.AllItems & ~CommandSupport.Contained; - Commands = new[] { "ExportSpawners" }; - ObjectTypes = ObjectTypes.Items; - Usage = "ExportSpawners"; - Description = "Exports the given the spawners to the a file"; - ListOptimized = true; - } - - public override void ExecuteList(CommandEventArgs e, List list) - { - var path = e.Arguments.Length == 0 ? null : e.Arguments[0].Trim(); - - if (string.IsNullOrEmpty(path)) + if (list[i] is BaseSpawner spawner) { - path = Path.Combine(Core.BaseDirectory, $"Data/Spawns/{Utility.GetTimeStamp()}.json"); - } - else - { - var directory = Path.GetDirectoryName(Path.GetFullPath(path!))!; - if (!Path.IsPathRooted(path)) - { - path = Path.Combine(Core.BaseDirectory, path); - PathUtility.EnsureDirectory(directory); - } - else if (!Directory.Exists(directory)) - { - LogFailure("Directory doesn't exist."); - return; - } - } - - NetState.FlushAll(); - - var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); - - var spawnRecords = new List(list.Count); - for (var i = 0; i < list.Count; i++) - { - if (list[i] is BaseSpawner spawner) + if (!string.IsNullOrEmpty(spawner.Name) && spawner.Name.StartsWith( + condition, + StringComparison.OrdinalIgnoreCase + )) { var dynamicJson = DynamicJson.Create(spawner.GetType()); spawner.ToJson(dynamicJson, options); spawnRecords.Add(dynamicJson); } } - - if (spawnRecords.Count == 0) - { - LogFailure("No matching spawners found."); - return; - } - - e.Mobile.SendMessage("Exporting spawners..."); - - JsonConfig.Serialize(path, spawnRecords, options); - - e.Mobile.SendMessage($"Spawners exported to {path}"); } + + if (spawnRecords.Count == 0) + { + LogFailure("No matching spawners found."); + return; + } + + e.Mobile.SendMessage("Exporting spawners..."); + + JsonConfig.Serialize(path, spawnRecords, options); + + e.Mobile.SendMessage($"Spawners exported to {path}"); } } diff --git a/Projects/UOContent/Engines/Spawners/SpawnerGump.cs b/Projects/UOContent/Engines/Spawners/SpawnerGump.cs index c1d075037..012899230 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnerGump.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnerGump.cs @@ -1,369 +1,447 @@ -using System.Collections.Generic; +using Server.Collections; using Server.Gumps; using Server.Network; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public class SpawnerGump : Gump { - public class SpawnerGump : Gump + private readonly BaseSpawner _spawner; + private SpawnerEntry _entry; + private int _page; + + public SpawnerGump(BaseSpawner spawner, SpawnerEntry focusentry = null, int page = 0) : base(50, 50) { - private readonly BaseSpawner m_Spawner; - private SpawnerEntry m_Entry; - private int m_Page; + _spawner = spawner; + _entry = focusentry; + _page = page; - public SpawnerGump(BaseSpawner spawner, SpawnerEntry focusentry = null, int page = 0) : base(50, 50) + AddPage(0); + + AddBackground(0, 0, 346, 400 + (_entry != null ? 44 : 0), 5054); + AddAlphaRegion(0, 0, 346, 400 + (_entry != null ? 44 : 0)); + + AddHtml(240, 1, 250, 20, "#"); + AddHtml(271, 1, 250, 20, "Max"); + AddHtml(311, 1, 250, 20, "Prb"); + + // AddLabel( 95, 1, 0, "Creatures List" ); + + var offset = 0; + + for (var i = 0; i < 13; i++) { - m_Spawner = spawner; - m_Entry = focusentry; - m_Page = page; + var textIndex = i * 5; + var entryIndex = _page * 13 + i; - AddPage(0); + SpawnerEntry entry = null; - AddBackground(0, 0, 343, 371 + (m_Entry != null ? 44 : 0), 5054); - - AddHtml(95, 1, 250, 20, "Creatures List"); - AddHtml(245, 1, 250, 20, "#"); - AddHtml(282, 1, 250, 20, "Prb"); - - // AddLabel( 95, 1, 0, "Creatures List" ); - - var offset = 0; - - for (var i = 0; i < 13; i++) + if (entryIndex < spawner.Entries.Count) { - var textindex = i * 5; - var entryindex = m_Page * 13 + i; + entry = _spawner.Entries[entryIndex]; + } - SpawnerEntry entry = null; + if (entry == null || _entry != entry) + { + AddButton( + 5, + 22 * i + 21 + offset, + entry != null ? 0xFBA : 0xFA5, + entry != null ? 0xFBC : 0xFA7, + GetButtonID(2, i * 2) + ); // Expand + } + else + { + AddButton( + 5, + 22 * i + 21 + offset, + 0xFBB, + 0xFBC, + GetButtonID(2, i * 2) + ); // Unexpand + } - if (entryindex < spawner.Entries.Count) + AddButton(38, 22 * i + 21 + offset, 0xFA2, 0xFA4, GetButtonID(2, 1 + i * 2)); // Delete + + AddImageTiled(71, 22 * i + 20 + offset, 161, 23, 0xA40); // creature text box + AddImageTiled(72, 22 * i + 21 + offset, 159, 21, 0xBBC); // creature text box + + AddImageTiled(235, 22 * i + 20 + offset, 35, 23, 0xA40); // count html label + AddImageTiled(236, 22 * i + 21 + offset, 33, 21, 0xE14); // count html label + + AddImageTiled(267, 22 * i + 20 + offset, 35, 23, 0xA40); // maxcount text box + AddImageTiled(268, 22 * i + 21 + offset, 33, 21, 0xBBC); // maxcount text box + + AddImageTiled(305, 22 * i + 20 + offset, 35, 23, 0xA40); // probability text box + AddImageTiled(306, 22 * i + 21 + offset, 33, 21, 0xBBC); // probability text box + + string name; + string probability; + string maxCount; + var flags = EntryFlags.None; + + if (entry != null) + { + name = entry.SpawnedName; + probability = entry.SpawnedProbability.ToString(); + maxCount = entry.SpawnedMaxCount.ToString(); + flags = entry.Valid; + + var count = spawner.CountSpawns(entry); + + AddHtml(235, 22 * i + 20 + offset + 1, 35, 15, $"
{count}/
"); + } + else + { + name = ""; + probability = ""; + maxCount = ""; + } + + // creature + AddTextEntry( + 75, + 22 * i + 21 + offset, + 156, + 21, + (flags & EntryFlags.InvalidType) != 0 ? 33 : 0, + textIndex, + name + ); + AddTextEntry(270, 22 * i + 21 + offset, 30, 21, 0, textIndex + 1, maxCount); // max count + AddTextEntry(308, 22 * i + 21 + offset, 30, 21, 0, textIndex + 2, probability); // probability + + if (entry != null && _entry == entry) + { + AddLabel(5, 22 * i + 42, 0x384, "Params"); + AddImageTiled(55, 22 * i + 42, 253, 23, 0xA40); // Parameters + AddImageTiled(56, 22 * i + 43, 251, 21, 0xBBC); // Parameters + + AddLabel(5, 22 * i + 64, 0x384, "Props"); + AddImageTiled(55, 22 * i + 64, 253, 23, 0xA40); // Properties + AddImageTiled(56, 22 * i + 65, 251, 21, 0xBBC); // Properties + + AddTextEntry( + 59, + 22 * i + 42, + 248, + 21, + (flags & EntryFlags.InvalidParams) != 0 ? 33 : 0, + textIndex + 3, + entry.Parameters + ); // parameters + AddTextEntry( + 59, + 22 * i + 62, + 248, + 21, + (flags & EntryFlags.InvalidProps) != 0 ? 33 : 0, + textIndex + 4, + entry.Properties + ); // properties + + offset += 44; + } + } + + if (spawner.Running) + { + AddButton(5, 312 + offset, 0x2A4E, 0x2A3A, GetButtonID(1, 6)); + AddLabel(38, 317 + offset, 0x384, "On"); + } + else + { + AddButton(5, 312 + offset, 0x2A62, 0x2A3A, GetButtonID(1, 7)); + AddLabel(38, 317 + offset, 0x384, "Off"); + } + + var totalSpawned = 0; + var totalSpawns = 0; + var totalWeight = 0; + + foreach (SpawnerEntry spawnerEntry in _spawner.Entries) + { + totalSpawns += spawnerEntry.SpawnedMaxCount; + totalSpawned += spawner.CountSpawns(spawnerEntry); + totalWeight += spawnerEntry.SpawnedProbability; + } + + AddHtml(270, 308 + offset, 35, 20, $"
{totalSpawns}
"); + AddHtml(308, 308 + offset, 35, 20, $"
{totalWeight}
"); + + AddHtml(5, 1, 161, 20, $"{spawner.Name} ({totalSpawned}/{spawner.Count})"); + + AddButton(5, 347 + offset, 0xFAB, 0xFAD, GetButtonID(1, 2)); + AddLabel(38, 347 + offset, 0x384, "Props"); + + AddButton(5, 369 + offset, 0xFAE, 0xFAF, GetButtonID(1, 8)); + AddLabel(38, 369 + offset, 0x384, "Goto"); + + AddButton(90, 325 + offset, 0xFA2, 0xFA3, GetButtonID(1, 9)); + AddLabel(123, 325 + offset, 0x384, "Reset"); + + AddButton(90, 347 + offset, 0xFB4, 0xFB6, GetButtonID(1, 3)); + AddLabel(123, 347 + offset, 0x384, "Bring Home"); + + AddButton(90, 369 + offset, 0xFA8, 0xFAA, GetButtonID(1, 4)); + AddLabel(123, 369 + offset, 0x384, "Total Respawn"); + + AddButton(260, 347 + offset, 0xFB7, 0xFB9, GetButtonID(1, 5)); + AddLabel(293, 347 + offset, 0x384, "Save"); + + AddButton(260, 369 + offset, 0xFB1, 0xFB3, 0); + AddLabel(293, 369 + offset, 0x384, "Cancel"); + + if (_page > 0) + { + AddButton(200, 308 + offset, 0x15E3, 0x15E7, GetButtonID(1, 0)); + } + else + { + AddImage(200, 308 + offset, 0x25EA); + } + + if ((_page + 1) * 13 <= _spawner.Entries.Count) + { + AddButton(217, 308 + offset, 0x15E1, 0x15E5, GetButtonID(1, 1)); + } + else + { + AddImage(217, 308 + offset, 0x25E6); + } + } + + public int GetButtonID(int type, int index) => 1 + index * 10 + type; + + private static string GetCountColor(int count, int maxCount) => + ((double) count / maxCount) switch + { + <= 0.25 => "#DE3163", // red + <= 0.50 => "#FF7F50", // orange + <= 0.75 => "#DFFF00", // yellow + <= 1 => "#00FF00", // green + _ => "#F4F4F4" // white + }; + + public void CreateArray(RelayInfo info, Mobile from, BaseSpawner spawner) + { + var ocount = spawner.Entries.Count; + + using var queue = PooledRefQueue.Create(); + + for (var i = 0; i < 13; i++) + { + var index = i * 5; + var entryindex = _page * 13 + i; + + var cte = info.GetTextEntry(index); + var mte = info.GetTextEntry(index + 1); + var poste = info.GetTextEntry(index + 2); + var parmte = info.GetTextEntry(index + 3); + var propte = info.GetTextEntry(index + 4); + + if (cte == null) + { + continue; + } + + var str = cte.Text.Trim().ToLower(); + + if (str.Length > 0) + { + var type = AssemblyHandler.FindTypeByName(str); + + if (type == null) { - entry = m_Spawner.Entries[entryindex]; + from.SendMessage("{0} is not a valid type name for entry #{1}.", str, i); + return; } - if (entry == null || m_Entry != entry) + SpawnerEntry entry; + + if (entryindex < ocount) { - AddButton( - 5, - 22 * i + 21 + offset, - entry != null ? 0xFBA : 0xFA5, - entry != null ? 0xFBC : 0xFA7, - GetButtonID(2, i * 2) - ); // Expand + entry = spawner.Entries[entryindex]; + entry.SpawnedName = str; + + if (mte != null) + { + entry.SpawnedMaxCount = Utility.ToInt32(mte.Text.Trim()); + } + + if (poste != null) + { + entry.SpawnedProbability = Utility.ToInt32(poste.Text.Trim()); + } } else { - AddButton( - 5, - 22 * i + 21 + offset, - 0xFBB, - 0xFBC, - GetButtonID(2, i * 2) - ); // Unexpand + var maxcount = 1; + var probcount = 100; + + if (mte != null) + { + maxcount = Utility.ToInt32(mte.Text.Trim()); + } + + if (poste != null) + { + probcount = Utility.ToInt32(poste.Text.Trim()); + } + + entry = spawner.AddEntry(str, probcount, maxcount); } - AddButton(38, 22 * i + 21 + offset, 0xFA2, 0xFA4, GetButtonID(2, 1 + i * 2)); // Delete - - AddImageTiled(71, 22 * i + 20 + offset, 161, 23, 0xA40); // creature text box - AddImageTiled(72, 22 * i + 21 + offset, 159, 21, 0xBBC); // creature text box - - AddImageTiled(235, 22 * i + 20 + offset, 35, 23, 0xA40); // maxcount text box - AddImageTiled(236, 22 * i + 21 + offset, 33, 21, 0xBBC); // maxcount text box - - AddImageTiled(273, 22 * i + 20 + offset, 35, 23, 0xA40); // probability text box - AddImageTiled(274, 22 * i + 21 + offset, 33, 21, 0xBBC); // probability text box - - var name = ""; - var probability = ""; - var maxcount = ""; - var flags = EntryFlags.None; - - if (entry != null) + if (parmte != null) { - name = entry.SpawnedName; - probability = entry.SpawnedProbability.ToString(); - maxcount = entry.SpawnedMaxCount.ToString(); - flags = entry.Valid; - - AddLabel(315, 22 * i + 20 + offset, 0, spawner.CountSpawns(entry).ToString()); + entry.Parameters = parmte.Text.Trim(); } - AddTextEntry( - 75, - 22 * i + 21 + offset, - 156, - 21, - (flags & EntryFlags.InvalidType) != 0 ? 33 : 0, - textindex, - name - ); // creature - AddTextEntry(239, 22 * i + 21 + offset, 30, 21, 0, textindex + 1, maxcount); // max count - AddTextEntry(277, 22 * i + 21 + offset, 30, 21, 0, textindex + 2, probability); // probability - - if (entry != null && m_Entry == entry) + if (propte != null) { - AddLabel(5, 22 * i + 42, 0x384, "Params"); - AddImageTiled(55, 22 * i + 42, 253, 23, 0xA40); // Parameters - AddImageTiled(56, 22 * i + 43, 251, 21, 0xBBC); // Parameters - - AddLabel(5, 22 * i + 64, 0x384, "Props"); - AddImageTiled(55, 22 * i + 64, 253, 23, 0xA40); // Properties - AddImageTiled(56, 22 * i + 65, 251, 21, 0xBBC); // Properties - - AddTextEntry( - 59, - 22 * i + 42, - 248, - 21, - (flags & EntryFlags.InvalidParams) != 0 ? 33 : 0, - textindex + 3, - entry.Parameters - ); // parameters - AddTextEntry( - 59, - 22 * i + 62, - 248, - 21, - (flags & EntryFlags.InvalidProps) != 0 ? 33 : 0, - textindex + 4, - entry.Properties - ); // properties - - offset += 44; + entry.Properties = propte.Text.Trim(); } } - - AddButton(5, 347 + offset, 0xFB1, 0xFB3, 0); - AddLabel(38, 347 + offset, 0x384, "Cancel"); - - AddButton(5, 325 + offset, 0xFB7, 0xFB9, GetButtonID(1, 2)); - AddLabel(38, 325 + offset, 0x384, "Okay"); - - AddButton(110, 325 + offset, 0xFB4, 0xFB6, GetButtonID(1, 3)); - AddLabel(143, 325 + offset, 0x384, "Bring to Home"); - - AddButton(110, 347 + offset, 0xFA8, 0xFAA, GetButtonID(1, 4)); - AddLabel(143, 347 + offset, 0x384, "Total Respawn"); - - AddButton(253, 325 + offset, 0xFB7, 0xFB9, GetButtonID(1, 5)); - AddLabel(286, 325 + offset, 0x384, "Apply"); - - if (m_Page > 0) + else if (entryindex < ocount && spawner.Entries[entryindex] != null) { - AddButton(276, 308 + offset, 0x15E3, 0x15E7, GetButtonID(1, 0)); - } - else - { - AddImage(276, 308 + offset, 0x25EA); - } - - if ((m_Page + 1) * 13 <= m_Spawner.Entries.Count) - { - AddButton(293, 308 + offset, 0x15E1, 0x15E5, GetButtonID(1, 1)); - } - else - { - AddImage(293, 308 + offset, 0x25E6); + queue.Enqueue(spawner.Entries[entryindex]); } } - public int GetButtonID(int type, int index) => 1 + index * 10 + type; - - public void CreateArray(RelayInfo info, Mobile from, BaseSpawner spawner) + while (queue.Count > 0) { - var ocount = spawner.Entries.Count; - - var rementries = new List(); - - for (var i = 0; i < 13; i++) - { - var index = i * 5; - var entryindex = m_Page * 13 + i; - - var cte = info.GetTextEntry(index); - var mte = info.GetTextEntry(index + 1); - var poste = info.GetTextEntry(index + 2); - var parmte = info.GetTextEntry(index + 3); - var propte = info.GetTextEntry(index + 4); - - if (cte == null) - { - continue; - } - - var str = cte.Text.Trim().ToLower(); - - if (str.Length > 0) - { - var type = AssemblyHandler.FindTypeByName(str); - - if (type == null) - { - from.SendMessage("{0} is not a valid type name for entry #{1}.", str, i); - return; - } - - SpawnerEntry entry; - - if (entryindex < ocount) - { - entry = spawner.Entries[entryindex]; - entry.SpawnedName = str; - - if (mte != null) - { - entry.SpawnedMaxCount = Utility.ToInt32(mte.Text.Trim()); - } - - if (poste != null) - { - entry.SpawnedProbability = Utility.ToInt32(poste.Text.Trim()); - } - } - else - { - var maxcount = 1; - var probcount = 100; - - if (mte != null) - { - maxcount = Utility.ToInt32(mte.Text.Trim()); - } - - if (poste != null) - { - probcount = Utility.ToInt32(poste.Text.Trim()); - } - - entry = spawner.AddEntry(str, probcount, maxcount); - } - - if (parmte != null) - { - entry.Parameters = parmte.Text.Trim(); - } - - if (propte != null) - { - entry.Properties = propte.Text.Trim(); - } - } - else if (entryindex < ocount && spawner.Entries[entryindex] != null) - { - rementries.Add(spawner.Entries[entryindex]); - } - } - - for (var i = 0; i < rementries.Count; i++) - { - spawner.RemoveEntry(rementries[i]); - } - - if (ocount == 0 && spawner.Entries.Count > 0) - { - spawner.Start(); - } + spawner.RemoveEntry(queue.Dequeue()); } - public override void OnResponse(NetState state, RelayInfo info) + if (ocount == 0 && spawner.Entries.Count > 0) { - if (m_Spawner.Deleted) - { - return; - } + spawner.Start(); + } + } - var val = info.ButtonID - 1; + public override void OnResponse(NetState state, RelayInfo info) + { + if (_spawner.Deleted) + { + return; + } - if (val < 0) - { - return; - } + var val = info.ButtonID - 1; - var type = val % 10; - var index = val / 10; + if (val < 0) + { + return; + } - switch (type) - { - case 0: // Cancel + var type = val % 10; + var index = val / 10; + + switch (type) + { + case 0: // Cancel + { return; - case 1: + } + case 1: + { + switch (index) { - switch (index) - { - case 0: + case 0: + { + if (_spawner.Entries != null && _page > 0) { - if (m_Spawner.Entries != null && m_Page > 0) - { - m_Page--; - m_Entry = null; - } + _page--; + _entry = null; + } - break; - } - case 1: + break; + } + case 1: + { + if ((_page + 1) * 13 <= _spawner.Entries?.Count) { - if ((m_Page + 1) * 13 <= m_Spawner.Entries?.Count) - { - m_Page++; - m_Entry = null; - } + _page++; + _entry = null; + } - break; - } - case 2: // Okay - { - CreateArray(info, state.Mobile, m_Spawner); - return; - } - case 3: - { - m_Spawner.BringToHome(); - break; - } - case 4: // Complete respawn - { - m_Spawner.Respawn(); - break; - } - case 5: - { - CreateArray(info, state.Mobile, m_Spawner); - break; - } - } + break; + } + case 2: // Props + { + state.Mobile.SendGump(new PropertiesGump(state.Mobile, _spawner)); + break; + } + case 3: // Bring Home + { + _spawner.BringToHome(); + break; + } + case 4: // Complete respawn + { + _spawner.Respawn(); + break; + } + case 5: // Save + { + CreateArray(info, state.Mobile, _spawner); + break; + } + case 6: // On button + { + _spawner.Running = false; - break; + break; + } + case 7: // Off button + { + _spawner.Running = true; + break; + } + case 8: // Goto + { + state.Mobile.MoveToWorld(_spawner.Location, _spawner.Map); + break; + } + case 9: // Reset + { + _spawner.Reset(); + break; + } } - case 2: + + break; + } + case 2: + { + var entryIndex = index / 2 + _page * 13; + var buttonType = index % 2; + + if (entryIndex >= 0 && entryIndex < _spawner.Entries.Count) { - var entryindex = index / 2 + m_Page * 13; - var buttontype = index % 2; - - if (entryindex >= 0 && entryindex < m_Spawner.Entries.Count) + var entry = _spawner.Entries[entryIndex]; + if (buttonType == 0) // Spawn creature { - var entry = m_Spawner.Entries[entryindex]; - if (buttontype == 0) // Spawn creature - { - m_Entry = m_Entry != entry ? entry : null; - } - else // Remove creatures - { - m_Spawner.RemoveSpawn(entryindex); - } + _entry = _entry != entry ? entry : null; + } + else // Remove creatures + { + _spawner.RemoveSpawn(entryIndex); } - - CreateArray(info, state.Mobile, m_Spawner); - break; } - } - if (m_Entry != null && m_Spawner.Entries?.Contains(m_Entry) == true) - { - state.Mobile.SendGump(new SpawnerGump(m_Spawner, m_Entry, m_Page)); - } - else - { - state.Mobile.SendGump(new SpawnerGump(m_Spawner, null, m_Page)); - } + CreateArray(info, state.Mobile, _spawner); + break; + } + } + + if (_entry != null && _spawner.Entries?.Contains(_entry) == true) + { + state.Mobile.SendGump(new SpawnerGump(_spawner, _entry, _page)); + } + else + { + state.Mobile.SendGump(new SpawnerGump(_spawner, null, _page)); } } }