From 6601101e2e1dc0e6b279fbf6b61528527d9d3621 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 30 Jun 2022 22:48:54 -0500 Subject: [PATCH] fix: Updates spawner gump visuals! Thanks Quick! (#1102) This update changes the default RunUO SpawnerGump's overall look and feel and expands slightly some functionality. Inspired by the XmlSpawner gump. ![image](https://user-images.githubusercontent.com/577652/176790254-cc1fb6bb-b281-49c9-a7cd-ec5376a69921.png) ![image](https://user-images.githubusercontent.com/577652/176790147-408ef682-9670-4753-872c-db843be1ab05.png) 1. The name of the spawner is displayed 2. This displays the total current spawn with the current allowed max spawn 3. Cleans up the display of the current count and the max count 4. The ability to turn on/off the spawner 5. Several changes to buttons - Simple Save and Cancel instead of Okay, Cancel and Apply. - Props button to display spawner properties - Goto button to teleport to the spawner - Reset button that will clear all the spawned mobs and turn off the spawner - Repositioned the buttons to make a little more sense 6. The sum of all the max spawn values per entry, from all pages --- .../UOContent/Engines/Spawners/BaseSpawner.cs | 2185 +++++++++-------- .../Commands/ExportSpawnersCommand.cs | 119 +- .../UOContent/Engines/Spawners/SpawnerGump.cs | 706 +++--- 3 files changed, 1550 insertions(+), 1460 deletions(-) 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)); } } }