From 5ada7a4e501619abefcf4ad3e64f38695ea9c481 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:14:42 -0800 Subject: [PATCH] fix: Codegens spawners (#1678) --- Projects/Server/Json/SerializedJsonIgnore.cs | 25 + .../Server/Json/SerializedJsonPropertyName.cs | 26 + .../UOContent/Engines/Spawners/BaseSpawner.cs | 547 +++++------------- .../Commands/ConvertPremiumSpawners.cs | 387 ++++++------- .../Spawners/Commands/EditSpawnerCommand.cs | 186 +++--- .../Commands/ExportSpawnersCommand.cs | 2 +- .../Commands/GenerateSpawnersCommand.cs | 325 ++++++----- .../Spawners/Commands/RespawnCommand.cs | 63 +- .../Spawners/Commands/SpawnPropsCommand.cs | 77 ++- .../Engines/Spawners/ProximitySpawner.cs | 263 ++++----- .../Engines/Spawners/RegionSpawner.cs | 330 +++++------ .../Engines/Spawners/SpawnPropsGump.cs | 233 ++++---- .../UOContent/Engines/Spawners/Spawner.cs | 247 ++++---- .../Engines/Spawners/SpawnerEntry.cs | 184 +++--- ...rver.Engines.Spawners.BaseSpawner.v10.json | 100 ++++ ....Engines.Spawners.ProximitySpawner.v0.json | 30 + ...ver.Engines.Spawners.RegionSpawner.v0.json | 14 + .../Server.Engines.Spawners.Spawner.v0.json | 4 + ...rver.Engines.Spawners.SpawnerEntry.v1.json | 55 ++ 19 files changed, 1494 insertions(+), 1604 deletions(-) create mode 100644 Projects/Server/Json/SerializedJsonIgnore.cs create mode 100644 Projects/Server/Json/SerializedJsonPropertyName.cs create mode 100644 Projects/UOContent/Migrations/Server.Engines.Spawners.BaseSpawner.v10.json create mode 100644 Projects/UOContent/Migrations/Server.Engines.Spawners.ProximitySpawner.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Engines.Spawners.RegionSpawner.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Engines.Spawners.Spawner.v0.json create mode 100644 Projects/UOContent/Migrations/Server.Engines.Spawners.SpawnerEntry.v1.json diff --git a/Projects/Server/Json/SerializedJsonIgnore.cs b/Projects/Server/Json/SerializedJsonIgnore.cs new file mode 100644 index 000000000..9165b6f77 --- /dev/null +++ b/Projects/Server/Json/SerializedJsonIgnore.cs @@ -0,0 +1,25 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2024 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SerializedJsonIgnore.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json.Serialization; +using ModernUO.Serialization; + +namespace Server.Json; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class SerializedJsonIgnoreAttribute : SerializedPropertyAttrAttribute +{ +} diff --git a/Projects/Server/Json/SerializedJsonPropertyName.cs b/Projects/Server/Json/SerializedJsonPropertyName.cs new file mode 100644 index 000000000..ec322d091 --- /dev/null +++ b/Projects/Server/Json/SerializedJsonPropertyName.cs @@ -0,0 +1,26 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2024 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SerializedJsonPropertyName.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json.Serialization; +using ModernUO.Serialization; + +namespace Server.Json; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class SerializedJsonPropertyNameAttribute(string name) : SerializedPropertyAttrAttribute +{ + public string Name { get; } = name; +} diff --git a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs index 2bfba1957..1371567b9 100644 --- a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text.Json; +using ModernUO.Serialization; using Server.Commands; using Server.Items; using Server.Json; @@ -12,21 +13,59 @@ using static Server.Attributes; namespace Server.Engines.Spawners; -public abstract class BaseSpawner : Item, ISpawner +[SerializationGenerator(10, false)] +public abstract partial class BaseSpawner : Item, ISpawner { - private static WarnTimer m_WarnTimer; - + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.Developer)] 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; + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.Developer)] + private bool _returnOnDeactivate; + + [SerializableField(2, setter: "private")] + private List _entries; + + [InvalidateProperties] + [SerializableField(3)] + [SerializedCommandProperty(AccessLevel.Developer)] + private int _walkingRange = -1; + + [SerializableField(4)] + [SerializedCommandProperty(AccessLevel.Developer)] + private WayPoint _wayPoint; + + [InvalidateProperties] + [SerializableField(5)] + [SerializedCommandProperty(AccessLevel.Developer)] + private bool _group; + + [InvalidateProperties] + [SerializableField(6)] + [SerializedCommandProperty(AccessLevel.Developer)] + private TimeSpan _minDelay; + + [InvalidateProperties] + [SerializableField(7)] + [SerializedCommandProperty(AccessLevel.Developer)] + private TimeSpan _maxDelay; + + [InvalidateProperties] + [SerializableField(9)] + [SerializedCommandProperty(AccessLevel.Developer)] + private int _team; + + [InvalidateProperties] + [SerializableField(10)] + [SerializedCommandProperty(AccessLevel.Developer)] + private int _homeRange; + + [SerializableField(12)] + [SerializedCommandProperty(AccessLevel.Developer)] + private DateTime _end; + + private InternalTimer _timer; public BaseSpawner() : this(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4) { @@ -88,7 +127,7 @@ public abstract class BaseSpawner : Item, ISpawner json.GetProperty("team", options, out int team); json.GetProperty("homeRange", options, out int homeRange); json.GetProperty("walkingRange", options, out int walkingRange); - m_WalkingRange = walkingRange; + _walkingRange = walkingRange; InitSpawn(amount, minDelay, maxDelay, team, homeRange); @@ -100,54 +139,40 @@ public abstract class BaseSpawner : Item, ISpawner } } - public BaseSpawner(Serial serial) : base(serial) - { - } - public override string DefaultName => "Spawner"; - public bool IsFull => Spawned?.Count >= m_Count; + public bool IsFull => Spawned?.Count >= _count; public bool IsEmpty => Spawned?.Count == 0; - public DateTime End { get; set; } - - public List Entries { get; private set; } public Dictionary Spawned { get; private set; } - [CommandProperty(AccessLevel.Developer)] - public Guid Guid - { - get => _guid; - set => _guid = value; - } - + [SerializableProperty(8)] [CommandProperty(AccessLevel.Developer)] public int Count { - get => m_Count; + get => _count; set { - m_Count = value; + _count = value; if (IsFull) { - m_Timer?.Stop(); + _timer?.Stop(); } - else if (m_Timer?.Running != true) + else if (_timer?.Running != true) { DoTimer(); } InvalidateProperties(); + this.MarkDirty(); } } - [CommandProperty(AccessLevel.Developer)] - public virtual WayPoint WayPoint { get; set; } - + [SerializableProperty(11)] [CommandProperty(AccessLevel.Developer)] public bool Running { - get => m_Running; + get => _running; set { if (value) @@ -160,57 +185,14 @@ public abstract class BaseSpawner : Item, ISpawner } 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(); + this.MarkDirty(); } } [CommandProperty(AccessLevel.Developer)] public TimeSpan NextSpawn { - get => m_Running && m_Timer?.Running == true ? End - Core.Now : TimeSpan.Zero; + get => _running && _timer?.Running == true ? End - Core.Now : TimeSpan.Zero; set { Start(); @@ -218,34 +200,9 @@ public abstract class BaseSpawner : Item, ISpawner } } - [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 UpdateEntries(List entries) @@ -266,7 +223,7 @@ public abstract class BaseSpawner : Item, ISpawner Spawned.Remove(spawn); } - if (m_Running && !IsFull && m_Timer?.Running == false) + if (_running && !IsFull && _timer?.Running == false) { DoTimer(); } @@ -276,7 +233,7 @@ public abstract class BaseSpawner : Item, ISpawner { RemoveSpawns(); - for (var i = 0; i < m_Count; i++) + for (var i = 0; i < _count; i++) { Spawn(); } @@ -339,7 +296,7 @@ public abstract class BaseSpawner : Item, ISpawner string parameters = null ) { - var entry = new SpawnerEntry(creaturename, probability, amount, properties, parameters); + var entry = new SpawnerEntry(this, creaturename, probability, amount, properties, parameters); Entries.Add(entry); if (dotimer) { @@ -353,13 +310,13 @@ public abstract class BaseSpawner : Item, ISpawner { 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; + _running = true; + _group = false; + _minDelay = minDelay; + _maxDelay = maxDelay; + _count = amount; + _team = team; + _homeRange = homeRange; Entries = new List(); Spawned = new Dictionary(); @@ -383,16 +340,16 @@ public abstract class BaseSpawner : Item, ISpawner { base.GetProperties(list); - if (m_Running) + if (_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~ + list.Add(1060656, _count); // amount to make: ~1_val~ + list.Add(1061169, _homeRange); // range ~1_val~ + list.Add(1050039, $"{"walking range:"}\t{_walkingRange}"); // ~1_NUMBER~ ~2_ITEMNAME~ + list.Add(1053099, $"{"group:"}\t{_group}"); // ~1_oretype~: ~2_armortype~ + list.Add(1060847, $"{"team:"}\t{_team}"); // ~1_val~ ~2_val~ + list.Add(1063483, $"{"delay:"}\t{_minDelay} to {_maxDelay}"); // ~1_MATERIAL~: ~2_ITEMNAME~ GetSpawnerProperties(list); @@ -412,7 +369,7 @@ public abstract class BaseSpawner : Item, ISpawner { base.OnSingleClick(from); - if (m_Running) + if (_running) { LabelTo(from, "[Running]"); } @@ -424,11 +381,11 @@ public abstract class BaseSpawner : Item, ISpawner public void Start() { - if (!m_Running) + if (!_running) { if (Entries.Count > 0) { - m_Running = true; + _running = true; DoTimer(); } } @@ -436,8 +393,8 @@ public abstract class BaseSpawner : Item, ISpawner public void Stop() { - m_Timer?.Stop(); - m_Running = false; + _timer?.Stop(); + _running = false; } public void Defrag() @@ -472,7 +429,7 @@ public abstract class BaseSpawner : Item, ISpawner public void OnTick() { - if (m_Group) + if (_group) { Defrag(); @@ -737,12 +694,12 @@ public abstract class BaseSpawner : Item, ISpawner { var walkrange = GetWalkingRange(); - c.RangeHome = walkrange >= 0 ? walkrange : m_HomeRange; + c.RangeHome = walkrange >= 0 ? walkrange : _homeRange; c.CurrentWayPoint = WayPoint; - if (m_Team > 0) + if (_team > 0) { - c.Team = m_Team; + c.Team = _team; } c.Home = Location; @@ -784,19 +741,19 @@ public abstract class BaseSpawner : Item, ISpawner return true; } - public virtual int GetWalkingRange() => m_WalkingRange; + public virtual int GetWalkingRange() => _walkingRange; public virtual Map GetSpawnMap() => Map; public void DoTimer() { - if (!m_Running) + if (!_running) { return; } - var min = (long)m_MinDelay.TotalMilliseconds; - var max = (long)m_MaxDelay.TotalMilliseconds; + var min = (long)_minDelay.TotalMilliseconds; + var max = (long)_maxDelay.TotalMilliseconds; var delay = TimeSpan.FromMilliseconds(Utility.RandomMinMax(min, max)); DoTimer(delay); @@ -804,26 +761,26 @@ public abstract class BaseSpawner : Item, ISpawner public virtual void DoTimer(TimeSpan delay) { - if (!m_Running) + if (!_running) { return; } End = Core.Now + delay; - if (m_Timer == null) + if (_timer == null) { - m_Timer = new InternalTimer(this, delay); + _timer = new InternalTimer(this, delay); } else { - m_Timer.Stop(); - m_Timer.Delay = delay; + _timer.Stop(); + _timer.Delay = delay; } if (!IsFull) { - m_Timer.Start(); + _timer.Start(); } } @@ -847,7 +804,7 @@ public abstract class BaseSpawner : Item, ISpawner Entries.Remove(entry); - if (m_Running && !IsFull && m_Timer?.Running == false) + if (_running && !IsFull && _timer?.Running == false) { DoTimer(); } @@ -899,7 +856,7 @@ public abstract class BaseSpawner : Item, ISpawner } } - if (m_Running && !IsFull && m_Timer?.Running == false) + if (_running && !IsFull && _timer?.Running == false) { DoTimer(); } @@ -925,294 +882,54 @@ public abstract class BaseSpawner : Item, ISpawner RemoveSpawns(); } - public override void Serialize(IGenericWriter writer) + private void Deserialize(IGenericReader reader, int version) { - 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) + _guid = reader.ReadGuid(); + _returnOnDeactivate = reader.ReadBool(); + _entries = new List(reader.ReadInt()); + + for (var i = 0; i < _entries.Count; ++i) { - Entries = new List(); + var entry = new SpawnerEntry(this); + entry.Deserialize(reader); + _entries.Add(entry); } - switch (version) + _walkingRange = reader.ReadInt(); + _wayPoint = reader.ReadEntity(); + _group = reader.ReadBool(); + _minDelay = reader.ReadTimeSpan(); + _maxDelay = reader.ReadTimeSpan(); + _count = reader.ReadInt(); + _team = reader.ReadInt(); + _homeRange = reader.ReadInt(); + _running = reader.ReadBool(); + + _end = _running ? reader.ReadDeltaTime() : Core.Now; + } + + [AfterDeserialization(false)] + private void AfterDeserialization() + { + if (_running && _end > Core.Now) { - case 9: - { - _guid = reader.ReadGuid(); - goto case 8; - } - case 8: - { - ReturnOnDeactivate = reader.ReadBool(); - goto case 7; - } - case 7: - { - var size = reader.ReadInt(); - - Entries = new List(size); - - for (var i = 0; i < size; ++i) - { - Entries.Add(new SpawnerEntry(this, reader)); - } - - goto case 4; // Skip the other crap - } - case 6: - { - var size = reader.ReadInt(); - - var addentries = Entries.Count == 0; - - for (var i = 0; i < size; ++i) - { - if (addentries) - { - Entries.Add(new SpawnerEntry(string.Empty, 100, reader.ReadInt())); - } - else - { - Entries[i].SpawnedMaxCount = reader.ReadInt(); - } - } - - 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(); - - 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); - } - } - - var count = reader.ReadInt(); - - for (var i = 0; i < count; ++i) - { - var e = reader.ReadEntity(); - - if (e == null) - { - 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; - } - } - } - } - - DoTimer(ts); - - break; - } - } - - if (version < 4) - { - m_WalkingRange = m_HomeRange; + DoTimer(_end - Core.Now); } } private class InternalTimer : Timer { - private readonly BaseSpawner m_Spawner; + private readonly BaseSpawner _spawner; - public InternalTimer(BaseSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner; + public InternalTimer(BaseSpawner spawner, TimeSpan delay) : base(delay) => _spawner = spawner; protected override void OnTick() { - if (m_Spawner?.Deleted == false) + if (_spawner?.Deleted == false) { - m_Spawner.OnTick(); - } - } - } - - private class WarnTimer : Timer - { - 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; + _spawner.OnTick(); } } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs index 9afa835a3..0fbc549fb 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs @@ -23,236 +23,235 @@ using Server.Json; using Server.Logging; using Server.Network; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public static class ConvertPremiumSpawners { - public static class ConvertPremiumSpawners + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ConvertPremiumSpawners)); + + public static void Configure() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(ConvertPremiumSpawners)); + CommandSystem.Register("ConvertPremiumSpawners", AccessLevel.Developer, ConvertPremiumSpawners_OnCommand); + } - public static void Configure() + [Usage("ConvertPremiumSpawners ")] + [Description("Converts Nerun's Premium Spawners to JSON.")] + private static void ConvertPremiumSpawners_OnCommand(CommandEventArgs args) + { + var from = args.Mobile; + + if (args.Arguments.Length != 2) { - CommandSystem.Register("ConvertPremiumSpawners", AccessLevel.Developer, ConvertPremiumSpawners_OnCommand); + from.SendMessage("Usage: [ConvertPremiumSpawners "); + return; } - [Usage("ConvertPremiumSpawners ")] - [Description("Converts Nerun's Premium Spawners to JSON.")] - private static void ConvertPremiumSpawners_OnCommand(CommandEventArgs args) + var inputDi = new DirectoryInfo(Core.BaseDirectory); + + var patternMatches = new Matcher() + .AddInclude(args.Arguments[0]) + .Execute(new DirectoryInfoWrapper(inputDi)) + .Files; + + List<(FileInfo, string)> files = []; + foreach (var match in patternMatches) { - var from = args.Mobile; - - if (args.Arguments.Length != 2) - { - from.SendMessage("Usage: [ConvertPremiumSpawners "); - return; - } - - var inputDi = new DirectoryInfo(Core.BaseDirectory); - - var patternMatches = new Matcher() - .AddInclude(args.Arguments[0]) - .Execute(new DirectoryInfoWrapper(inputDi)) - .Files; - - List<(FileInfo, string)> files = new List<(FileInfo, string)>(); - foreach (var match in patternMatches) - { - files.Add((new FileInfo(match.Path), match.Stem)); - } - - if (files.Count == 0) - { - from.SendMessage("ConvertPremiumSpawners: No files found."); - return; - } - - var outputDir = Path.Combine(Core.BaseDirectory, args.Arguments[1]); - var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); - - for (var i = 0; i < files.Count; i++) - { - var (file, stem) = files[i]; - from.SendMessage($"ConvertPremiumSpawners: Converting spawners for {stem}..."); - - NetState.FlushAll(); - - try - { - var stemDir = stem[..^file.Name.Length]; - var fullOutputDir = Path.Combine(outputDir, stemDir); - PathUtility.EnsureDirectory(fullOutputDir); - - var outputFileName = $"{file.Name.AsSpan(0, file.Name.Length - file.Extension.Length)}.json"; - ParsePremiumSpawnerFile(file.FullName, Path.Combine(fullOutputDir, outputFileName), options); - } - catch (Exception e) - { - logger.Error(e.ToString()); - from.SendMessage($"ConvertPremiumSpawners: Exception parsing {stem}, file may not be in the correct format."); - } - } + files.Add((new FileInfo(match.Path), match.Stem)); } - private static void ParsePremiumSpawnerFile(string inputFile, string outputFile, JsonSerializerOptions options) + if (files.Count == 0) { - var lines = File.ReadAllLines(inputFile); + from.SendMessage("ConvertPremiumSpawners: No files found."); + return; + } - TimeSpan minTime = TimeSpan.MinValue; - TimeSpan maxTime = TimeSpan.MinValue; - int mapId = -1; - string spawnerId = null; + var outputDir = Path.Combine(Core.BaseDirectory, args.Arguments[1]); + var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); - var json = new List(); + for (var i = 0; i < files.Count; i++) + { + var (file, stem) = files[i]; + from.SendMessage($"ConvertPremiumSpawners: Converting spawners for {stem}..."); - foreach (var line in lines) + NetState.FlushAll(); + + try { - if (line.StartsWithOrdinal("#")) - { - continue; - } + var stemDir = stem[..^file.Name.Length]; + var fullOutputDir = Path.Combine(outputDir, stemDir); + PathUtility.EnsureDirectory(fullOutputDir); - if (line.StartsWith('*')) + var outputFileName = $"{file.Name.AsSpan(0, file.Name.Length - file.Extension.Length)}.json"; + ParsePremiumSpawnerFile(file.FullName, Path.Combine(fullOutputDir, outputFileName), options); + } + catch (Exception e) + { + logger.Error(e.ToString()); + from.SendMessage($"ConvertPremiumSpawners: Exception parsing {stem}, file may not be in the correct format."); + } + } + } + + private static void ParsePremiumSpawnerFile(string inputFile, string outputFile, JsonSerializerOptions options) + { + var lines = File.ReadAllLines(inputFile); + + TimeSpan minTime = TimeSpan.MinValue; + TimeSpan maxTime = TimeSpan.MinValue; + int mapId = -1; + string spawnerId = null; + + var json = new List(); + + foreach (var line in lines) + { + if (line.StartsWithOrdinal("#")) + { + continue; + } + + if (line.StartsWith('*')) + { + var spawners = ParsePremiumSpawner(line.Split('|'), spawnerId, mapId, minTime, maxTime); + foreach (var spawner in spawners) { - var spawners = ParsePremiumSpawner(line.Split('|'), spawnerId, mapId, minTime, maxTime); - foreach (var spawner in spawners) + var dynamicJson = new DynamicJson { - var dynamicJson = new DynamicJson - { - Type = "Spawner", - Data = new Dictionary() - }; + Type = "Spawner", + Data = new Dictionary() + }; - spawner.ToJson(dynamicJson, options); - json.Add(dynamicJson); - spawner.Delete(); - } - - spawners.Clear(); - continue; - } - - var over = line.Split(' '); - switch (over[0].ToLowerInvariant()) - { - case "overrideid": - { - spawnerId = over[1]; - break; - } - case "overridemap": - { - mapId = int.Parse(over[1]); - break; - } - case "overridemintime": - { - minTime = GetTimeSpan(over[1]); - break; - } - case "overridemaxtime": - { - maxTime = GetTimeSpan(over[1]); - break; - } - } - } - - JsonConfig.Serialize(outputFile, json, options); - } - - private static List ParsePremiumSpawner( - string[] parts, - string spawnerIdOverride, - int mapIdOverride, - TimeSpan minTimeOverride, - TimeSpan maxTimeOverride - ) - { - var spawnersList = new List(); - var mapId = mapIdOverride != -1 ? mapIdOverride : int.Parse(parts[10]); - Map[] maps = mapId == 0 ? new[] { Map.Felucca, Map.Trammel } : new[] { Map.Maps[mapId - 1] }; - - foreach (var map in maps) - { - var spawner = new Spawner - { - X = int.Parse(parts[7]), - Y = int.Parse(parts[8]), - Z = int.Parse(parts[9]), - Map = map, - MinDelay = minTimeOverride != TimeSpan.MinValue ? minTimeOverride : GetTimeSpan(parts[11]), - MaxDelay = maxTimeOverride != TimeSpan.MinValue ? maxTimeOverride : GetTimeSpan(parts[12]), - WalkingRange = int.Parse(parts[13]), - HomeRange = int.Parse(parts[14]), - Name = $"Spawner ({spawnerIdOverride ?? parts[15]})", - Guid = Guid.NewGuid() - }; - - var totalCount = 0; - - for (var i = 0; i < 6; i++) - { - var count = int.Parse(parts[i + 16]); - totalCount += count; - spawner.Entries.AddRange(CreateSpawnerEntries(parts[i + 1], count)); - } - - if (spawner.Entries.Count == 0) - { + spawner.ToJson(dynamicJson, options); + json.Add(dynamicJson); spawner.Delete(); - continue; } - spawner.Count = totalCount; - spawnersList.Add(spawner); + spawners.Clear(); + continue; } - return spawnersList; + var over = line.Split(' '); + switch (over[0].ToLowerInvariant()) + { + case "overrideid": + { + spawnerId = over[1]; + break; + } + case "overridemap": + { + mapId = int.Parse(over[1]); + break; + } + case "overridemintime": + { + minTime = GetTimeSpan(over[1]); + break; + } + case "overridemaxtime": + { + maxTime = GetTimeSpan(over[1]); + break; + } + } } - private static List CreateSpawnerEntries(string typeList, int maxCount) + JsonConfig.Serialize(outputFile, json, options); + } + + private static List ParsePremiumSpawner( + string[] parts, + string spawnerIdOverride, + int mapIdOverride, + TimeSpan minTimeOverride, + TimeSpan maxTimeOverride + ) + { + var spawnersList = new List(); + var mapId = mapIdOverride != -1 ? mapIdOverride : int.Parse(parts[10]); + Map[] maps = mapId == 0 ? [Map.Felucca, Map.Trammel] : [Map.Maps[mapId - 1]]; + + foreach (var map in maps) { - var list = new List(); - if (string.IsNullOrWhiteSpace(typeList)) + var spawner = new Spawner { - return list; + X = int.Parse(parts[7]), + Y = int.Parse(parts[8]), + Z = int.Parse(parts[9]), + Map = map, + MinDelay = minTimeOverride != TimeSpan.MinValue ? minTimeOverride : GetTimeSpan(parts[11]), + MaxDelay = maxTimeOverride != TimeSpan.MinValue ? maxTimeOverride : GetTimeSpan(parts[12]), + WalkingRange = int.Parse(parts[13]), + HomeRange = int.Parse(parts[14]), + Name = $"Spawner ({spawnerIdOverride ?? parts[15]})", + Guid = Guid.NewGuid() + }; + + var totalCount = 0; + + for (var i = 0; i < 6; i++) + { + var count = int.Parse(parts[i + 16]); + totalCount += count; + spawner.Entries.AddRange(CreateSpawnerEntries(spawner, parts[i + 1], count)); } - foreach (var spawnType in typeList.Split(':')) + if (spawner.Entries.Count == 0) { - var actualType = AssemblyHandler.FindTypeByName(ConvertType(spawnType))?.Name ?? spawnType; - list.Add(new SpawnerEntry(actualType, 100, maxCount)); + spawner.Delete(); + continue; } + spawner.Count = totalCount; + spawnersList.Add(spawner); + } + + return spawnersList; + } + + private static List CreateSpawnerEntries(BaseSpawner spawner, string typeList, int maxCount) + { + var list = new List(); + if (string.IsNullOrWhiteSpace(typeList)) + { return list; } - private static TimeSpan GetTimeSpan(string time) + foreach (var spawnType in typeList.Split(':')) { - if (string.IsNullOrEmpty(time)) - { - return TimeSpan.MinValue; - } - - return char.ToLowerInvariant(time[^1]) switch - { - 'h' => TimeSpan.FromHours(double.Parse(time.AsSpan(0, time.Length - 1))), - 'm' => TimeSpan.FromMinutes(double.Parse(time.AsSpan(0, time.Length - 1))), - 's' => TimeSpan.FromSeconds(double.Parse(time.AsSpan(0, time.Length - 1))), - _ => TimeSpan.FromMinutes(double.Parse(time)) - }; + var actualType = AssemblyHandler.FindTypeByName(ConvertType(spawnType))?.Name ?? spawnType; + list.Add(new SpawnerEntry(spawner, actualType, 100, maxCount)); } - private static string ConvertType(string type) - { - return type.ToLowerInvariant() switch - { - "treasurelevel1" => "treasurechestlevel1", - "treasurelevel2" => "treasurechestlevel2", - "treasurelevel3" => "treasurechestlevel3", - "treasurelevel4" => "treasurechestlevel4", - "treasurelevel5" => "treasurechestlevel5", - _ => type - }; - } + return list; } -} + + private static TimeSpan GetTimeSpan(string time) + { + if (string.IsNullOrEmpty(time)) + { + return TimeSpan.MinValue; + } + + return char.ToLowerInvariant(time[^1]) switch + { + 'h' => TimeSpan.FromHours(double.Parse(time.AsSpan(0, time.Length - 1))), + 'm' => TimeSpan.FromMinutes(double.Parse(time.AsSpan(0, time.Length - 1))), + 's' => TimeSpan.FromSeconds(double.Parse(time.AsSpan(0, time.Length - 1))), + _ => TimeSpan.FromMinutes(double.Parse(time)) + }; + } + + private static string ConvertType(string type) + { + return type.ToLowerInvariant() switch + { + "treasurelevel1" => "treasurechestlevel1", + "treasurelevel2" => "treasurechestlevel2", + "treasurelevel3" => "treasurechestlevel3", + "treasurelevel4" => "treasurechestlevel4", + "treasurelevel5" => "treasurechestlevel5", + _ => type + }; + } +} \ No newline at end of file diff --git a/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs index 4c7f26646..a51188f74 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs @@ -18,116 +18,114 @@ using System.Collections.Generic; using Server.Commands.Generic; using static Server.Types; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public class EditSpawnCommand : BaseCommand { - - public class EditSpawnCommand : BaseCommand + public static void Configure() { - public static void Configure() + TargetCommands.Register(new EditSpawnCommand()); + } + + public EditSpawnCommand() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.Complex | CommandSupport.Simple; + Commands = ["EditSpawner"]; + ObjectTypes = ObjectTypes.Items; + Usage = "EditSpawner set where "; + Description = "Modifies spawners arguments and properties for the given type"; + ListOptimized = true; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + var args = e.Arguments; + + if (args.Length <= 1) { - TargetCommands.Register(new EditSpawnCommand()); + LogFailure(Usage); + return; } - public EditSpawnCommand() + if (list.Count == 0) { - AccessLevel = AccessLevel.GameMaster; - Supports = CommandSupport.Complex | CommandSupport.Simple; - Commands = new[] { "EditSpawner" }; - ObjectTypes = ObjectTypes.Items; - Usage = "EditSpawner set where "; - Description = "Modifies spawners arguments and properties for the given type"; - ListOptimized = true; + LogFailure("No matching objects found."); + return; } - public override void ExecuteList(CommandEventArgs e, List list) + var name = args[0]; + + var type = AssemblyHandler.FindTypeByName(name); + + if (!IsEntity(type)) { - var args = e.Arguments; - - if (args.Length <= 1) - { - LogFailure(Usage); - return; - } - - if (list.Count == 0) - { - LogFailure("No matching objects found."); - return; - } - - var name = args[0]; - - var type = AssemblyHandler.FindTypeByName(name); - - if (!IsEntity(type)) - { - LogFailure("No type with that name was found."); - return; - } - - var argSpan = e.ArgString.AsSpan(name.Length + 1); - var setIndex = argSpan.InsensitiveIndexOf("set "); - var where = argSpan.InsensitiveIndexOf("where "); - - ReadOnlySpan props = null, findmatch = null; - - if (setIndex > -1 || where > -1) - { - var start = setIndex + 4; - var len = where > -1 ? where : argSpan.Length; - findmatch = argSpan.Slice(len < argSpan.Length ? len + 6 : argSpan.Length); - if (setIndex > -1) - { - props = argSpan[start..^len]; - argSpan = argSpan[..setIndex]; - } - else - { - argSpan = argSpan[..^len]; - } - } - - var argStr = argSpan.ToString().DefaultIfNullOrEmpty(null); - var propsStr = props.ToString().DefaultIfNullOrEmpty(null); - var whereStr = findmatch.ToString().DefaultIfNullOrEmpty(null); - - e.Mobile.SendMessage("Updating spawners..."); - - foreach (var obj in list) - { - if (obj is BaseSpawner spawner) - { - UpdateSpawner(spawner, name, argStr, propsStr, whereStr); - } - } - - e.Mobile.SendMessage("Update completed."); + LogFailure("No type with that name was found."); + return; } - public static void UpdateSpawner(BaseSpawner spawner, string name, string arguments, string properties, string find = null) + var argSpan = e.ArgString.AsSpan(name.Length + 1); + var setIndex = argSpan.InsensitiveIndexOf("set "); + var where = argSpan.InsensitiveIndexOf("where "); + + ReadOnlySpan props = null, findmatch = null; + + if (setIndex > -1 || where > -1) { - foreach (var entry in spawner.Entries) + var start = setIndex + 4; + var len = where > -1 ? where : argSpan.Length; + findmatch = argSpan.Slice(len < argSpan.Length ? len + 6 : argSpan.Length); + if (setIndex > -1) { - // TODO: Should cache spawn type on the entry - if (!entry.SpawnedName.InsensitiveEquals(name)) + props = argSpan[start..^len]; + argSpan = argSpan[..setIndex]; + } + else + { + argSpan = argSpan[..^len]; + } + } + + var argStr = argSpan.ToString().DefaultIfNullOrEmpty(null); + var propsStr = props.ToString().DefaultIfNullOrEmpty(null); + var whereStr = findmatch.ToString().DefaultIfNullOrEmpty(null); + + e.Mobile.SendMessage("Updating spawners..."); + + foreach (var obj in list) + { + if (obj is BaseSpawner spawner) + { + UpdateSpawner(spawner, name, argStr, propsStr, whereStr); + } + } + + e.Mobile.SendMessage("Update completed."); + } + + public static void UpdateSpawner(BaseSpawner spawner, string name, string arguments, string properties, string find = null) + { + foreach (var entry in spawner.Entries) + { + // TODO: Should cache spawn type on the entry + if (!entry.SpawnedName.InsensitiveEquals(name)) + { + continue; + } + + var found = find != null ? entry.Properties?.LastIndexOf(find, StringComparison.OrdinalIgnoreCase) : null; + var shouldUpdate = find == null || found > -1 && + (found + find.Length == entry.Properties.Length || + char.IsWhiteSpace(entry.Properties[(int)found + find.Length])); + + if (shouldUpdate) + { + if (arguments != null) { - continue; + entry.Parameters = arguments; } - var found = find != null ? entry.Properties?.LastIndexOf(find, StringComparison.OrdinalIgnoreCase) : null; - var shouldUpdate = find == null || found > -1 && - (found + find.Length == entry.Properties.Length || - char.IsWhiteSpace(entry.Properties[(int)found + find.Length])); - - if (shouldUpdate) - { - if (arguments != null) - { - entry.Parameters = arguments; - } - - entry.Properties = properties; - } + entry.Properties = properties; } } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs index d9cc910b4..588cb98a6 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs @@ -33,7 +33,7 @@ public class ExportSpawnersCommand : BaseCommand { AccessLevel = AccessLevel.GameMaster; Supports = CommandSupport.AllItems & ~CommandSupport.Contained; - Commands = new[] { "ExportSpawners" }; + Commands = ["ExportSpawners"]; ObjectTypes = ObjectTypes.Items; Usage = "ExportSpawners"; Description = "Exports the given spawners to a file"; diff --git a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs index 2b43e2fa0..757dc8b6d 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs @@ -11,194 +11,193 @@ using Server.Logging; using Server.Network; using Server.Utilities; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public static class GenerateSpawnersCommand { - public static class GenerateSpawnersCommand + private static readonly ILogger logger = LogFactory.GetLogger(typeof(GenerateSpawnersCommand)); + + public static void Configure() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(GenerateSpawnersCommand)); + CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand); + } - public static void Configure() + [Usage("GenerateSpawners ")] + [Aliases("ImportSpawners", "GenSpawners", "GenSpawns")] + [Description("Generates spawners from JSON files. Supports basic globbing.")] + private static void GenerateSpawners_OnCommand(CommandEventArgs e) + { + var from = e.Mobile; + + if (e.Arguments.Length == 0) { - CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand); + from.SendMessage("Usage: [GenerateSpawners "); + return; } - [Usage("GenerateSpawners ")] - [Aliases("ImportSpawners", "GenSpawners", "GenSpawns")] - [Description("Generates spawners from JSON files. Supports basic globbing.")] - private static void GenerateSpawners_OnCommand(CommandEventArgs e) + var di = new DirectoryInfo(Core.BaseDirectory); + + var patternMatches = new Matcher() + .AddInclude(e.Arguments[0]) + .Execute(new DirectoryInfoWrapper(di)) + .Files; + + List files = []; + foreach (var match in patternMatches) { - var from = e.Mobile; - - if (e.Arguments.Length == 0) - { - from.SendMessage("Usage: [GenerateSpawners "); - return; - } - - var di = new DirectoryInfo(Core.BaseDirectory); - - var patternMatches = new Matcher() - .AddInclude(e.Arguments[0]) - .Execute(new DirectoryInfoWrapper(di)) - .Files; - - List files = new List(); - foreach (var match in patternMatches) - { - files.Add(new FileInfo(match.Path)); - } - - if (files.Count == 0) - { - from.SendMessage("GenerateSpawners: No files found matching the pattern"); - return; - } - - var watch = Stopwatch.StartNew(); - - var allSpawners = new Dictionary(); - foreach (var item in World.Items.Values) - { - if (item is ISpawner spawner) - { - allSpawners[spawner.Guid] = spawner; - } - } - - var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); - var totalGenerated = 0; - var totalFailures = 0; - - for (var i = 0; i < files.Count; i++) - { - var file = files[i]; - from.SendMessage($"GenerateSpawners: Generating spawners from {file.Name}..."); - logger.Information("{User} is generating spawners from {File}", from, file.FullName); - - NetState.FlushAll(); - - try - { - var spawners = JsonConfig.Deserialize>(file.FullName); - ParseSpawnerList(spawners, options, allSpawners, out var generated, out var failed); - totalGenerated += generated; - totalFailures += failed; - } - catch (JsonException) - { - from.SendMessage( - $"GenerateSpawners: Exception parsing {file.FullName}, file may not be in the correct format." - ); - } - } - - watch.Stop(); - - logger.Information( - "Generated {Count} spawners ({Duration:F2} seconds, {Failures} failures)", - totalGenerated, - watch.Elapsed.TotalSeconds, - totalFailures - ); - - from.SendMessage( - $"GenerateSpawners: Generated {totalGenerated} spawners ({watch.Elapsed.TotalSeconds:F2} seconds, {totalFailures} failures)" - ); + files.Add(new FileInfo(match.Path)); } - private static void ParseSpawnerList( - List spawners, - JsonSerializerOptions options, - Dictionary allSpawners, - out int totalGenerated, - out int failureCount - ) + if (files.Count == 0) { - failureCount = 0; - totalGenerated = 0; + from.SendMessage("GenerateSpawners: No files found matching the pattern"); + return; + } - using var queue = PooledRefQueue.Create(); - for (var i = 0; i < spawners.Count; i++) + var watch = Stopwatch.StartNew(); + + var allSpawners = new Dictionary(); + foreach (var item in World.Items.Values) + { + if (item is ISpawner spawner) { - var json = spawners[i]; - var type = AssemblyHandler.FindTypeByName(json.Type); - - if (type == null || !typeof(BaseSpawner).IsAssignableFrom(type)) - { - logger.Error($"Invalid spawner type {json.Type ?? "(-null-)"} ({i})."); - failureCount++; - continue; - } - - json.GetProperty("location", options, out Point3D location); - json.GetProperty("map", options, out Map map); - - // Delete all spawners at this location. - // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? - foreach (var spawner in map.GetItemsAt(location)) - { - if (spawner.GetType() == type) - { - queue.Enqueue(spawner); - allSpawners.Remove(spawner.Guid); - } - } - - while (queue.Count > 0) - { - queue.Dequeue().Delete(); - } - - try - { - var spawner = type.CreateInstance(json, options); - - spawner!.MoveToWorld(location, map); - spawner!.Respawn(); - - if (allSpawners.Remove(spawner.Guid, out var oldSpawner)) - { - oldSpawner.Delete(); - } - - allSpawners.Add(spawner.Guid, spawner); - totalGenerated++; - } - catch (Exception ex) - { - json.GetProperty("guid", options, out Guid guid); - TraceException(ex, $"Failed to generate spawner {guid}."); - - failureCount++; - } + allSpawners[spawner.Guid] = spawner; } } - private static void TraceException(Exception ex, string message = "") + var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory()); + var totalGenerated = 0; + var totalFailures = 0; + + for (var i = 0; i < files.Count; i++) { + var file = files[i]; + from.SendMessage($"GenerateSpawners: Generating spawners from {file.Name}..."); + logger.Information("{User} is generating spawners from {File}", from, file.FullName); + + NetState.FlushAll(); + try { - using var op = new StreamWriter("spawner-errors.log", true); - op.WriteLine("# {0}", Core.Now); + var spawners = JsonConfig.Deserialize>(file.FullName); + ParseSpawnerList(spawners, options, allSpawners, out var generated, out var failed); + totalGenerated += generated; + totalFailures += failed; + } + catch (JsonException) + { + from.SendMessage( + $"GenerateSpawners: Exception parsing {file.FullName}, file may not be in the correct format." + ); + } + } - if (!string.IsNullOrEmpty(message)) + watch.Stop(); + + logger.Information( + "Generated {Count} spawners ({Duration:F2} seconds, {Failures} failures)", + totalGenerated, + watch.Elapsed.TotalSeconds, + totalFailures + ); + + from.SendMessage( + $"GenerateSpawners: Generated {totalGenerated} spawners ({watch.Elapsed.TotalSeconds:F2} seconds, {totalFailures} failures)" + ); + } + + private static void ParseSpawnerList( + List spawners, + JsonSerializerOptions options, + Dictionary allSpawners, + out int totalGenerated, + out int failureCount + ) + { + failureCount = 0; + totalGenerated = 0; + + using var queue = PooledRefQueue.Create(); + for (var i = 0; i < spawners.Count; i++) + { + var json = spawners[i]; + var type = AssemblyHandler.FindTypeByName(json.Type); + + if (type == null || !typeof(BaseSpawner).IsAssignableFrom(type)) + { + logger.Error($"Invalid spawner type {json.Type ?? "(-null-)"} ({i})."); + failureCount++; + continue; + } + + json.GetProperty("location", options, out Point3D location); + json.GetProperty("map", options, out Map map); + + // Delete all spawners at this location. + // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? + foreach (var spawner in map.GetItemsAt(location)) + { + if (spawner.GetType() == type) { - op.WriteLine(message); + queue.Enqueue(spawner); + allSpawners.Remove(spawner.Guid); + } + } + + while (queue.Count > 0) + { + queue.Dequeue().Delete(); + } + + try + { + var spawner = type.CreateInstance(json, options); + + spawner!.MoveToWorld(location, map); + spawner!.Respawn(); + + if (allSpawners.Remove(spawner.Guid, out var oldSpawner)) + { + oldSpawner.Delete(); } - op.WriteLine(ex); - - op.WriteLine(); - op.WriteLine(); + allSpawners.Add(spawner.Guid, spawner); + totalGenerated++; } - catch + catch (Exception ex) { - // ignored - } + json.GetProperty("guid", options, out Guid guid); + TraceException(ex, $"Failed to generate spawner {guid}."); -#if DEBUG - logger.Error(ex, message); -#endif + failureCount++; + } } } + + private static void TraceException(Exception ex, string message = "") + { + try + { + using var op = new StreamWriter("spawner-errors.log", true); + op.WriteLine("# {0}", Core.Now); + + if (!string.IsNullOrEmpty(message)) + { + op.WriteLine(message); + } + + op.WriteLine(ex); + + op.WriteLine(); + op.WriteLine(); + } + catch + { + // ignored + } + +#if DEBUG + logger.Error(ex, message); +#endif + } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs index b9b386dc5..bfb4c6f2b 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs @@ -17,47 +17,46 @@ using System.Collections.Generic; using Server.Commands.Generic; using Server.Network; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public class RespawnCommand : BaseCommand { - public class RespawnCommand : BaseCommand + public static void Configure() { - public static void Configure() + TargetCommands.Register(new RespawnCommand()); + } + + public RespawnCommand() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.Complex | CommandSupport.Simple; + Commands = ["Respawn"]; + ObjectTypes = ObjectTypes.Items; + Usage = "Respawn"; + Description = "Respawns the given the spawners."; + ListOptimized = true; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + if (list.Count == 0) { - TargetCommands.Register(new RespawnCommand()); + LogFailure("No matching objects found."); + return; } - public RespawnCommand() - { - AccessLevel = AccessLevel.GameMaster; - Supports = CommandSupport.Complex | CommandSupport.Simple; - Commands = new[] { "Respawn" }; - ObjectTypes = ObjectTypes.Items; - Usage = "Respawn"; - Description = "Respawns the given the spawners."; - ListOptimized = true; - } + e.Mobile.SendMessage("Respawning..."); - public override void ExecuteList(CommandEventArgs e, List list) + NetState.FlushAll(); + + foreach (var obj in list) { - if (list.Count == 0) + if (obj is ISpawner spawner) { - LogFailure("No matching objects found."); - return; + spawner.Respawn(); } - - e.Mobile.SendMessage("Respawning..."); - - NetState.FlushAll(); - - foreach (var obj in list) - { - if (obj is ISpawner spawner) - { - spawner.Respawn(); - } - } - - e.Mobile.SendMessage("Respawn completed."); } + + e.Mobile.SendMessage("Respawn completed."); } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs index 0e6ca82ab..dd1d834ee 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs @@ -20,54 +20,53 @@ using Server.Targeting; using static Server.Types; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +public class SpawnPropsCommand : BaseCommand { - public class SpawnPropsCommand : BaseCommand + public static void Configure() { - public static void Configure() + TargetCommands.Register(new SpawnPropsCommand()); + } + + public SpawnPropsCommand() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.Complex | CommandSupport.Simple; + Commands = ["SpawnProps"]; + ObjectTypes = ObjectTypes.Items; + Usage = "SpawnProps"; + Description = "Shows a props gump that will modify the properties of spawn entries related to the chosen entity"; + ListOptimized = true; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + if (list.Count == 0) { - TargetCommands.Register(new SpawnPropsCommand()); + LogFailure("No matching objects found."); + return; } - public SpawnPropsCommand() - { - AccessLevel = AccessLevel.GameMaster; - Supports = CommandSupport.Complex | CommandSupport.Simple; - Commands = new[] { "SpawnProps" }; - ObjectTypes = ObjectTypes.Items; - Usage = "SpawnProps"; - Description = "Shows a props gump that will modify the properties of spawn entries related to the chosen entity"; - ListOptimized = true; - } + e.Mobile.SendMessage("Target the object you want to use as a template for modifying the spawner properties."); + e.Mobile.Target = new InternalTarget(list); + } - public override void ExecuteList(CommandEventArgs e, List list) + private class InternalTarget : Target + { + private readonly List _list; + + public InternalTarget(List list) : base(-1, false, TargetFlags.None) => _list = list; + + protected override void OnTarget(Mobile from, object targeted) { - if (list.Count == 0) + var type = targeted.GetType(); + if (!IsEntity(type)) { - LogFailure("No matching objects found."); - return; + from.SendMessage("No type with that name was found."); } - e.Mobile.SendMessage("Target the object you want to use as a template for modifying the spawner properties."); - e.Mobile.Target = new InternalTarget(list); - } - - private class InternalTarget : Target - { - private readonly List _list; - - public InternalTarget(List list) : base(-1, false, TargetFlags.None) => _list = list; - - protected override void OnTarget(Mobile from, object targeted) - { - var type = targeted.GetType(); - if (!IsEntity(type)) - { - from.SendMessage("No type with that name was found."); - } - - from.SendGump(new SpawnPropsGump(from, targeted, _list)); - } + from.SendGump(new SpawnPropsGump(from, targeted, _list)); } } -} +} \ No newline at end of file diff --git a/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs b/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs index 7fca167c1..83acb3393 100644 --- a/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs +++ b/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs @@ -15,174 +15,147 @@ using System; using System.Text.Json; +using ModernUO.Serialization; using Server.Json; using Server.Mobiles; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +[SerializationGenerator(0)] +public partial class ProximitySpawner : Spawner { - public class ProximitySpawner : Spawner + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.Developer)] + private int _triggerRange; + + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.Developer)] + private TextDefinition _spawnMessage; + + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.Developer)] + private bool _instantFlag; + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner() { - [Constructible(AccessLevel.Developer)] - public ProximitySpawner() + } + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner(string spawnName) : base(spawnName) + { + } + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnName) + : base(amount, minDelay, maxDelay, team, homeRange, spawnName) + { + } + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner( + int amount, int minDelay, int maxDelay, int team, int homeRange, int triggerRange, + string spawnMessage, bool instantFlag, string spawnName + ) : base(amount, minDelay, maxDelay, team, homeRange, spawnName) + { + TriggerRange = triggerRange; + SpawnMessage = TextDefinition.Parse(spawnMessage); + InstantFlag = instantFlag; + } + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner( + int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) + { + } + + [Constructible(AccessLevel.Developer)] + public ProximitySpawner( + int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, int triggerRange, + TextDefinition spawnMessage, bool instantFlag, params string[] spawnedNames + ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) + { + TriggerRange = triggerRange; + SpawnMessage = spawnMessage; + InstantFlag = instantFlag; + } + + public ProximitySpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) + { + json.GetProperty("triggerRange", options, out int triggerRange); + json.GetProperty("spawnMessage", options, out TextDefinition spawnMessage); + json.GetProperty("instant", options, out bool instant); + + TriggerRange = triggerRange; + SpawnMessage = spawnMessage; + InstantFlag = instant; + } + + public override string DefaultName => "Proximity Spawner"; + + public override bool HandlesOnMovement => true; + + public override void ToJson(DynamicJson json, JsonSerializerOptions options) + { + json.SetProperty("triggerRange", options, TriggerRange); + json.SetProperty("spawnMessage", options, SpawnMessage); + json.SetProperty("instant", options, InstantFlag); + } + + public override void DoTimer(TimeSpan delay) + { + if (!Running) { + return; } - [Constructible(AccessLevel.Developer)] - public ProximitySpawner(string spawnName) - : base(spawnName) + End = Core.Now + delay; + } + + public override void Respawn() + { + RemoveSpawns(); + + End = Core.Now; + } + + public virtual bool ValidTrigger(Mobile m) + { + if (m is BaseCreature bc && (bc.IsDeadBondedPet || !(bc.Controlled || bc.Summoned))) { + return false; } - [Constructible(AccessLevel.Developer)] - public ProximitySpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnName) - : base(amount, minDelay, maxDelay, team, homeRange, spawnName) + return m.AccessLevel == AccessLevel.Player && (m.Player || m.Alive && !m.Hidden && m.CanBeDamaged()); + } + + public override void OnMovement(Mobile m, Point3D oldLocation) + { + if (!Running) { + return; } - [Constructible(AccessLevel.Developer)] - public ProximitySpawner( - int amount, int minDelay, int maxDelay, int team, int homeRange, int triggerRange, - string spawnMessage, bool instantFlag, string spawnName - ) - : base(amount, minDelay, maxDelay, team, homeRange, spawnName) + if (IsEmpty && End <= Core.Now && m.InRange(GetWorldLocation(), TriggerRange) && + m.Location != oldLocation && ValidTrigger(m)) { - TriggerRange = triggerRange; - SpawnMessage = TextDefinition.Parse(spawnMessage); - InstantFlag = instantFlag; - } + SpawnMessage.SendMessageTo(m); - [Constructible(AccessLevel.Developer)] - public ProximitySpawner( - int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, - params string[] spawnedNames - ) - : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) - { - } + DoTimer(); + Spawn(); - [Constructible(AccessLevel.Developer)] - public ProximitySpawner( - int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, int triggerRange, - TextDefinition spawnMessage, bool instantFlag, params string[] spawnedNames - ) - : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) - { - TriggerRange = triggerRange; - SpawnMessage = spawnMessage; - InstantFlag = instantFlag; - } - - public ProximitySpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) - { - json.GetProperty("triggerRange", options, out int triggerRange); - json.GetProperty("spawnMessage", options, out TextDefinition spawnMessage); - json.GetProperty("instant", options, out bool instant); - - TriggerRange = triggerRange; - SpawnMessage = spawnMessage; - InstantFlag = instant; - } - - public ProximitySpawner(Serial serial) - : base(serial) - { - } - - [CommandProperty(AccessLevel.Developer)] - public int TriggerRange { get; set; } - - [CommandProperty(AccessLevel.Developer)] - public TextDefinition SpawnMessage { get; set; } - - [CommandProperty(AccessLevel.Developer)] - public bool InstantFlag { get; set; } - - public override string DefaultName => "Proximity Spawner"; - - public override bool HandlesOnMovement => true; - - public override void ToJson(DynamicJson json, JsonSerializerOptions options) - { - json.SetProperty("triggerRange", options, TriggerRange); - json.SetProperty("spawnMessage", options, SpawnMessage); - json.SetProperty("instant", options, InstantFlag); - } - - public override void DoTimer(TimeSpan delay) - { - if (!Running) + if (InstantFlag) { - return; - } - - End = Core.Now + delay; - } - - public override void Respawn() - { - RemoveSpawns(); - - End = Core.Now; - } - - public virtual bool ValidTrigger(Mobile m) - { - if (m is BaseCreature bc && (bc.IsDeadBondedPet || !(bc.Controlled || bc.Summoned))) - { - return false; - } - - return m.AccessLevel == AccessLevel.Player && (m.Player || m.Alive && !m.Hidden && m.CanBeDamaged()); - } - - public override void OnMovement(Mobile m, Point3D oldLocation) - { - if (!Running) - { - return; - } - - if (IsEmpty && End <= Core.Now && m.InRange(GetWorldLocation(), TriggerRange) && - m.Location != oldLocation && ValidTrigger(m)) - { - SpawnMessage.SendMessageTo(m); - - DoTimer(); - Spawn(); - - if (InstantFlag) + foreach (var spawned in Spawned.Keys) { - foreach (var spawned in Spawned.Keys) + if (spawned is Mobile mobile) { - if (spawned is Mobile mobile) - { - mobile.Combatant = m; - } + mobile.Combatant = m; } } } } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - - writer.Write(TriggerRange); - writer.Write(SpawnMessage); - writer.Write(InstantFlag); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - TriggerRange = reader.ReadInt(); - SpawnMessage = reader.ReadTextDefinition(); - InstantFlag = reader.ReadBool(); - } } } diff --git a/Projects/UOContent/Engines/Spawners/RegionSpawner.cs b/Projects/UOContent/Engines/Spawners/RegionSpawner.cs index 4af59f1f5..3232ef833 100644 --- a/Projects/UOContent/Engines/Spawners/RegionSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/RegionSpawner.cs @@ -15,184 +15,172 @@ using System; using System.Text.Json; +using ModernUO.Serialization; using Server.Json; using Server.Regions; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +[SerializationGenerator(0)] +public partial class RegionSpawner : Spawner { - public class RegionSpawner : Spawner + [SerializableField(0, getter: "private", setter: "private")] + private string _spawnRegionName; + + private BaseRegion _spawnRegion; + + [Constructible(AccessLevel.Developer)] + public RegionSpawner() { - private BaseRegion m_SpawnRegion; + } - [Constructible(AccessLevel.Developer)] - public RegionSpawner() + [Constructible(AccessLevel.Developer)] + public RegionSpawner(string spawnedName) : base(spawnedName) + { + } + + [Constructible(AccessLevel.Developer)] + public RegionSpawner( + int amount, int minDelay, int maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : this( + amount, + TimeSpan.FromMinutes(minDelay), + TimeSpan.FromMinutes(maxDelay), + team, + homeRange, + spawnedNames + ) + { + } + + [Constructible(AccessLevel.Developer)] + public RegionSpawner( + int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) + { + } + + public RegionSpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) + { + json.GetProperty("map", options, out Map map); + json.GetProperty("region", options, out string spawnRegion); + + _spawnRegion = Region.Find(spawnRegion, map) as BaseRegion; + _spawnRegion?.InitRectangles(); + } + + [CommandProperty(AccessLevel.Developer)] + public BaseRegion SpawnRegion + { + get => _spawnRegion; + set { - } - - [Constructible(AccessLevel.Developer)] - public RegionSpawner(string spawnedName) : base(spawnedName) - { - } - - [Constructible(AccessLevel.Developer)] - public RegionSpawner( - int amount, int minDelay, int maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : this( - amount, - TimeSpan.FromMinutes(minDelay), - TimeSpan.FromMinutes(maxDelay), - team, - homeRange, - spawnedNames - ) - { - } - - [Constructible(AccessLevel.Developer)] - public RegionSpawner( - int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) - { - } - - public RegionSpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) - { - json.GetProperty("map", options, out Map map); - json.GetProperty("region", options, out string spawnRegion); - - m_SpawnRegion = Region.Find(spawnRegion, map) as BaseRegion; - m_SpawnRegion?.InitRectangles(); - } - - public RegionSpawner(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.Developer)] - public BaseRegion SpawnRegion - { - get => m_SpawnRegion; - set - { - m_SpawnRegion = value; - m_SpawnRegion?.InitRectangles(); - - InvalidateProperties(); - } - } - - public override void ToJson(DynamicJson json, JsonSerializerOptions options) - { - json.SetProperty("region", options, SpawnRegion.Name); - } - - public override void GetSpawnerProperties(IPropertyList list) - { - base.GetSpawnerProperties(list); - - if (Running && m_SpawnRegion != null) - { - list.Add(1076228, $"{"region:"}\t{m_SpawnRegion.Name}"); // ~1_DUMMY~ ~2_DUMMY~ - } - } - - public override Point3D GetSpawnPosition(ISpawnable spawned, Map map) - { - if (m_SpawnRegion == null || map == null || map == Map.Internal || map != m_SpawnRegion.Map || - m_SpawnRegion.TotalWeight <= 0) - { - return Location; - } - - bool waterMob, waterOnlyMob; - - if (spawned is Mobile mob) - { - waterMob = mob.CanSwim; - waterOnlyMob = mob.CanSwim && mob.CantWalk; - } - else - { - waterMob = false; - waterOnlyMob = false; - } - - // Try 10 times to find a valid location. - for (var i = 0; i < 10; i++) - { - var rand = Utility.Random(m_SpawnRegion.TotalWeight); - - var x = int.MinValue; - var y = int.MinValue; - - for (var j = 0; j < m_SpawnRegion.RectangleWeights.Length; j++) - { - var curWeight = m_SpawnRegion.RectangleWeights[j]; - - if (rand < curWeight) - { - var rect = m_SpawnRegion.Rectangles[j]; - - x = rect.Start.X + rand % rect.Width; - y = rect.Start.Y + rand / rect.Width; - - break; - } - - rand -= curWeight; - } - - var mapZ = map.GetAverageZ(x, y); - - if (waterMob) - { - if (IsValidWater(map, x, y, Z)) - { - return new Point3D(x, y, Z); - } - - if (IsValidWater(map, x, y, mapZ)) - { - return new Point3D(x, y, mapZ); - } - } - - if (!waterOnlyMob) - { - if (map.CanSpawnMobile(x, y, Z)) - { - return new Point3D(x, y, Z); - } - - if (map.CanSpawnMobile(x, y, mapZ)) - { - return new Point3D(x, y, mapZ); - } - } - } - - return HomeLocation; - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - m_SpawnRegion = Region.Find(reader.ReadString(), Map) as BaseRegion; - m_SpawnRegion?.InitRectangles(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - - writer.Write(m_SpawnRegion?.Name); + _spawnRegion = value; + SpawnRegionName = _spawnRegion?.Name; + _spawnRegion?.InitRectangles(); + InvalidateProperties(); } } + + public override void ToJson(DynamicJson json, JsonSerializerOptions options) + { + json.SetProperty("region", options, SpawnRegion.Name); + } + + public override void GetSpawnerProperties(IPropertyList list) + { + base.GetSpawnerProperties(list); + + if (Running && _spawnRegion != null) + { + list.Add(1076228, $"{"region:"}\t{_spawnRegion.Name}"); // ~1_DUMMY~ ~2_DUMMY~ + } + } + + public override Point3D GetSpawnPosition(ISpawnable spawned, Map map) + { + if (_spawnRegion == null || map == null || map == Map.Internal || map != _spawnRegion.Map || + _spawnRegion.TotalWeight <= 0) + { + return Location; + } + + bool waterMob, waterOnlyMob; + + if (spawned is Mobile mob) + { + waterMob = mob.CanSwim; + waterOnlyMob = mob.CanSwim && mob.CantWalk; + } + else + { + waterMob = false; + waterOnlyMob = false; + } + + // Try 10 times to find a valid location. + for (var i = 0; i < 10; i++) + { + var rand = Utility.Random(_spawnRegion.TotalWeight); + + var x = int.MinValue; + var y = int.MinValue; + + for (var j = 0; j < _spawnRegion.RectangleWeights.Length; j++) + { + var curWeight = _spawnRegion.RectangleWeights[j]; + + if (rand < curWeight) + { + var rect = _spawnRegion.Rectangles[j]; + + x = rect.Start.X + rand % rect.Width; + y = rect.Start.Y + rand / rect.Width; + + break; + } + + rand -= curWeight; + } + + var mapZ = map.GetAverageZ(x, y); + + if (waterMob) + { + if (IsValidWater(map, x, y, Z)) + { + return new Point3D(x, y, Z); + } + + if (IsValidWater(map, x, y, mapZ)) + { + return new Point3D(x, y, mapZ); + } + } + + if (!waterOnlyMob) + { + if (map.CanSpawnMobile(x, y, Z)) + { + return new Point3D(x, y, Z); + } + + if (map.CanSpawnMobile(x, y, mapZ)) + { + return new Point3D(x, y, mapZ); + } + } + } + + return HomeLocation; + } + + [AfterDeserialization(false)] + private void AfterDeserialization() + { + _spawnRegion = Region.Find(_spawnRegionName, Map) as BaseRegion; + _spawnRegion?.InitRectangles(); + } } diff --git a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs index edfb99ec0..dc7a425c2 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs @@ -20,134 +20,133 @@ using Server.Mobiles; using Server.Network; using Server.Text; -namespace Server.Gumps +namespace Server.Gumps; + +public class SpawnPropsGump : PropertiesGump { - public class SpawnPropsGump : PropertiesGump + public static readonly HashSet MobileAttributes = new() { - public static readonly HashSet MobileAttributes = new() - { - nameof(Mobile.Hue), - nameof(Mobile.Str), - nameof(Mobile.Dex), - nameof(Mobile.Int), - nameof(BaseCreature.HitsMaxSeed), - nameof(BaseCreature.Hits), - nameof(BaseCreature.DamageMin), - nameof(BaseCreature.DamageMax), - nameof(BaseCreature.ActiveSpeed), - nameof(BaseCreature.PassiveSpeed), - nameof(BaseCreature.VirtualArmor) - }; + nameof(Mobile.Hue), + nameof(Mobile.Str), + nameof(Mobile.Dex), + nameof(Mobile.Int), + nameof(BaseCreature.HitsMaxSeed), + nameof(BaseCreature.Hits), + nameof(BaseCreature.DamageMin), + nameof(BaseCreature.DamageMax), + nameof(BaseCreature.ActiveSpeed), + nameof(BaseCreature.PassiveSpeed), + nameof(BaseCreature.VirtualArmor) + }; - private List _spawners; + private List _spawners; - public SpawnPropsGump(Mobile mobile, object o, List spawners) : base(mobile, o) + public SpawnPropsGump(Mobile mobile, object o, List spawners) : base(mobile, o) + { + _spawners = spawners; + } + + public SpawnPropsGump(Mobile mobile, object o, Stack stack, StackEntry parent, List spawners) : base( + mobile, o, stack, parent + ) + { + _spawners = spawners; + } + + public SpawnPropsGump(Mobile mobile, object o, Stack stack, List list, int page, List spawners) : base( + mobile, o, stack, list, page + ) + { + _spawners = spawners; + } + + protected override int TotalHeight => base.TotalHeight + PropsConfig.ApplySize; + + protected override void Initialize(int page) + { + base.Initialize(page); + var totalHeight = TotalHeight - PropsConfig.ApplySize; + + AddButton(BackWidth / 3, PropsConfig.BorderSize + totalHeight + PropsConfig.BorderSize, 5204, 5205, 3); + } + + public override void SendPropertiesGump() => + m_Mobile.SendGump(new SpawnPropsGump(m_Mobile, m_Object, m_Stack, m_List, m_Page, _spawners)); + + public static object GetPropValue(object src, string propName) => + src.GetType().GetProperty(propName)?.GetValue(src, null); + + public override void OnResponse(NetState state, RelayInfo info) + { + var from = state.Mobile; + + if (!BaseCommand.IsAccessible(from, m_Object)) { - _spawners = spawners; + from.SendMessage("You may no longer access their properties."); + return; } - public SpawnPropsGump(Mobile mobile, object o, Stack stack, StackEntry parent, List spawners) : base( - mobile, o, stack, parent - ) + switch (info.ButtonID) { - _spawners = spawners; - } - - public SpawnPropsGump(Mobile mobile, object o, Stack stack, List list, int page, List spawners) : base( - mobile, o, stack, list, page - ) - { - _spawners = spawners; - } - - protected override int TotalHeight => base.TotalHeight + PropsConfig.ApplySize; - - protected override void Initialize(int page) - { - base.Initialize(page); - var totalHeight = TotalHeight - PropsConfig.ApplySize; - - AddButton(BackWidth / 3, PropsConfig.BorderSize + totalHeight + PropsConfig.BorderSize, 5204, 5205, 3); - } - - public override void SendPropertiesGump() => - m_Mobile.SendGump(new SpawnPropsGump(m_Mobile, m_Object, m_Stack, m_List, m_Page, _spawners)); - - public static object GetPropValue(object src, string propName) => - src.GetType().GetProperty(propName)?.GetValue(src, null); - - public override void OnResponse(NetState state, RelayInfo info) - { - var from = state.Mobile; - - if (!BaseCommand.IsAccessible(from, m_Object)) - { - from.SendMessage("You may no longer access their properties."); - return; - } - - switch (info.ButtonID) - { - default: - { - base.OnResponse(state, info); - break; - } - case 3: // Apply - { - using var propsBuilder = ValueStringBuilder.Create(); - bool first = true; - foreach (var attr in MobileAttributes) - { - var prop = GetPropValue(m_Object, attr); - if (prop != null) - { - if (first) - { - first = false; - } - else - { - propsBuilder.Append(' '); - } - - propsBuilder.Append(attr); - propsBuilder.Append(' '); - propsBuilder.Append(prop.ToString()); // TODO: Replace with ZString, or IFormatter code - } - } - - var name = m_Object.GetType().Name; - var props = propsBuilder.ToString(); - - m_Mobile.SendMessage("Updating spawners..."); - - foreach (var obj in _spawners) - { - if (obj is BaseSpawner spawner) - { - EditSpawnCommand.UpdateSpawner(spawner, name, null, props); - } - } - - m_Mobile.SendMessage("Update completed."); - - break; - } - } - } - - protected override bool ShowAttribute(string name) - { - foreach (var item in MobileAttributes) - { - if (item.InsensitiveEquals(name)) + default: { - return true; + base.OnResponse(state, info); + break; } - } + case 3: // Apply + { + using var propsBuilder = ValueStringBuilder.Create(); + bool first = true; + foreach (var attr in MobileAttributes) + { + var prop = GetPropValue(m_Object, attr); + if (prop != null) + { + if (first) + { + first = false; + } + else + { + propsBuilder.Append(' '); + } - return false; + propsBuilder.Append(attr); + propsBuilder.Append(' '); + propsBuilder.Append(prop.ToString()); // TODO: Replace with ZString, or IFormatter code + } + } + + var name = m_Object.GetType().Name; + var props = propsBuilder.ToString(); + + m_Mobile.SendMessage("Updating spawners..."); + + foreach (var obj in _spawners) + { + if (obj is BaseSpawner spawner) + { + EditSpawnCommand.UpdateSpawner(spawner, name, null, props); + } + } + + m_Mobile.SendMessage("Update completed."); + + break; + } } } + + protected override bool ShowAttribute(string name) + { + foreach (var item in MobileAttributes) + { + if (item.InsensitiveEquals(name)) + { + return true; + } + } + + return false; + } } diff --git a/Projects/UOContent/Engines/Spawners/Spawner.cs b/Projects/UOContent/Engines/Spawners/Spawner.cs index 203187c6d..5a5b1d1f2 100644 --- a/Projects/UOContent/Engines/Spawners/Spawner.cs +++ b/Projects/UOContent/Engines/Spawners/Spawner.cs @@ -1,162 +1,145 @@ using System; using System.Text.Json; +using ModernUO.Serialization; using Server.Json; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +[SerializationGenerator(0)] +public partial class Spawner : BaseSpawner { - public class Spawner : BaseSpawner + [Constructible(AccessLevel.Developer)] + public Spawner() { - [Constructible(AccessLevel.Developer)] - public Spawner() + } + + [Constructible(AccessLevel.Developer)] + public Spawner(string spawnedName) : base(spawnedName) + { + } + + [Constructible(AccessLevel.Developer)] + public Spawner( + int amount, int minDelay, int maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : this( + amount, + TimeSpan.FromMinutes(minDelay), + TimeSpan.FromMinutes(maxDelay), + team, + homeRange, + spawnedNames + ) + { + } + + [Constructible(AccessLevel.Developer)] + public Spawner( + int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, + params string[] spawnedNames + ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) + { + } + + public Spawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) + { + } + + public static bool IsValidWater(Map map, int x, int y, int z) + { + if (!Region.Find(new Point3D(x, y, z), map).AllowSpawn() || !map.CanFit(x, y, z, 16, false, true, false)) { - } - - [Constructible(AccessLevel.Developer)] - public Spawner(string spawnedName) : base(spawnedName) - { - } - - [Constructible(AccessLevel.Developer)] - public Spawner( - int amount, int minDelay, int maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : this( - amount, - TimeSpan.FromMinutes(minDelay), - TimeSpan.FromMinutes(maxDelay), - team, - homeRange, - spawnedNames - ) - { - } - - [Constructible(AccessLevel.Developer)] - public Spawner( - int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, - params string[] spawnedNames - ) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames) - { - } - - public Spawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) - { - } - - public Spawner(Serial serial) : base(serial) - { - } - - public static bool IsValidWater(Map map, int x, int y, int z) - { - if (!Region.Find(new Point3D(x, y, z), map).AllowSpawn() || !map.CanFit(x, y, z, 16, false, true, false)) - { - return false; - } - - var landTile = map.Tiles.GetLandTile(x, y); - - if (landTile.Z == z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet) != 0) - { - return true; - } - - foreach (var staticTile in map.Tiles.GetStaticAndMultiTiles(x, y)) - { - if (staticTile.Z == z && TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Wet) - { - return true; - } - } - return false; } - /* - public override bool OnDefragSpawn(ISpawnable spawned, bool remove) - { - // To despawn a mob that was lured 4x away from its spawner - // TODO: Move this to a config - if (spawned is BaseCreature c && c.Combatant == null && c.GetDistanceToSqrt( Location ) > c.RangeHome * 4) - { - c.Delete(); - remove = true; - } + var landTile = map.Tiles.GetLandTile(x, y); - return base.OnDefragSpawn(entry, spawned, remove); + if (landTile.Z == z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet) != 0) + { + return true; } - */ - public override Point3D GetSpawnPosition(ISpawnable spawned, Map map) + foreach (var staticTile in map.Tiles.GetStaticAndMultiTiles(x, y)) { - if (map == null || map == Map.Internal) + if (staticTile.Z == z && TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Wet) { - return Location; + return true; } + } - bool waterMob, waterOnlyMob; + return false; + } - if (spawned is Mobile mob) + /* + public override bool OnDefragSpawn(ISpawnable spawned, bool remove) + { + // To despawn a mob that was lured 4x away from its spawner + // TODO: Move this to a config + if (spawned is BaseCreature c && c.Combatant == null && c.GetDistanceToSqrt( Location ) > c.RangeHome * 4) + { + c.Delete(); + remove = true; + } + + return base.OnDefragSpawn(entry, spawned, remove); + } + */ + + public override Point3D GetSpawnPosition(ISpawnable spawned, Map map) + { + if (map == null || map == Map.Internal) + { + return Location; + } + + bool waterMob, waterOnlyMob; + + if (spawned is Mobile mob) + { + waterMob = mob.CanSwim; + waterOnlyMob = mob.CanSwim && mob.CantWalk; + } + else + { + waterMob = false; + waterOnlyMob = false; + } + + // Try 10 times to find a valid location. + for (var i = 0; i < 10; i++) + { + var x = Location.X + (Utility.Random(HomeRange * 2 + 1) - HomeRange); + var y = Location.Y + (Utility.Random(HomeRange * 2 + 1) - HomeRange); + + var mapZ = map.GetAverageZ(x, y); + + if (waterMob) { - waterMob = mob.CanSwim; - waterOnlyMob = mob.CanSwim && mob.CantWalk; - } - else - { - waterMob = false; - waterOnlyMob = false; - } - - // Try 10 times to find a valid location. - for (var i = 0; i < 10; i++) - { - var x = Location.X + (Utility.Random(HomeRange * 2 + 1) - HomeRange); - var y = Location.Y + (Utility.Random(HomeRange * 2 + 1) - HomeRange); - - var mapZ = map.GetAverageZ(x, y); - - if (waterMob) + if (IsValidWater(map, x, y, Z)) { - if (IsValidWater(map, x, y, Z)) - { - return new Point3D(x, y, Z); - } - - if (IsValidWater(map, x, y, mapZ)) - { - return new Point3D(x, y, mapZ); - } + return new Point3D(x, y, Z); } - if (!waterOnlyMob) + if (IsValidWater(map, x, y, mapZ)) { - if (map.CanSpawnMobile(x, y, Z)) - { - return new Point3D(x, y, Z); - } - - if (map.CanSpawnMobile(x, y, mapZ)) - { - return new Point3D(x, y, mapZ); - } + return new Point3D(x, y, mapZ); } } - return HomeLocation; + if (!waterOnlyMob) + { + if (map.CanSpawnMobile(x, y, Z)) + { + return new Point3D(x, y, Z); + } + + if (map.CanSpawnMobile(x, y, mapZ)) + { + return new Point3D(x, y, mapZ); + } + } } - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); // version - } + return HomeLocation; } } diff --git a/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs b/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs index 003aad405..bb09a677f 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs @@ -1,122 +1,104 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using ModernUO.Serialization; +using Server.Json; -namespace Server.Engines.Spawners +namespace Server.Engines.Spawners; + +[SerializationGenerator(1, false)] +public partial class SpawnerEntry { - public class SpawnerEntry + [DirtyTrackingEntity] + private BaseSpawner _parent; + + [SerializableField(0)] + [SerializedJsonPropertyName("name")] + private string _spawnedName; + + [SerializableField(1)] + [SerializedJsonPropertyName("probability")] + private int _spawnedProbability; + + [SerializableField(2)] + [SerializedJsonPropertyName("maxCount")] + private int _spawnedMaxCount; + + [SerializableField(3)] + [SerializedJsonPropertyName("properties")] + private string _properties; + + [SerializableField(4)] + [SerializedJsonPropertyName("parameters")] + private string _parameters; + + [SerializedJsonIgnore] + [SerializableField(5)] + private List _spawned; + + public SpawnerEntry(BaseSpawner parent) { - public SpawnerEntry() => Spawned = new List(); + _parent = parent; + _spawned = new List(); + } - public SpawnerEntry( - string name, - int probability, - int maxcount, - string properties = null, - string parameters = null - ) : this() + public SpawnerEntry( + BaseSpawner parent, + string name, + int probability, + int maxcount, + string properties = null, + string parameters = null + ) : this(parent) + { + SpawnedName = name; + SpawnedProbability = probability; + SpawnedMaxCount = maxcount; + Properties = properties; + Parameters = parameters; + } + + private void Deserialize(IGenericReader reader, int version) + { + SpawnedName = reader.ReadString(); + SpawnedProbability = reader.ReadInt(); + SpawnedMaxCount = reader.ReadInt(); + + Properties = reader.ReadString(); + Parameters = reader.ReadString(); + + var count = reader.ReadInt(); + + Spawned = new List(count); + + for (var i = 0; i < count; ++i) { - SpawnedName = name; - SpawnedProbability = probability; - SpawnedMaxCount = maxcount; - Properties = properties; - Parameters = parameters; - } + var e = reader.ReadEntity(); - public SpawnerEntry(BaseSpawner parent, IGenericReader reader) - { - var version = reader.ReadInt(); - - SpawnedName = reader.ReadString(); - SpawnedProbability = reader.ReadInt(); - SpawnedMaxCount = reader.ReadInt(); - - Properties = reader.ReadString(); - Parameters = reader.ReadString(); - - var count = reader.ReadInt(); - - Spawned = new List(count); - - for (var i = 0; i < count; ++i) + if (e != null) { - var e = reader.ReadEntity(); + e.Spawner = _parent; - if (e != null) - { - e.Spawner = parent; - - Spawned.Add(e); - parent.Spawned.TryAdd(e, this); - } + Spawned.Add(e); + _parent.Spawned.TryAdd(e, this); } } + } - [JsonPropertyName("name")] - public string SpawnedName { get; set; } + [JsonIgnore] + public EntryFlags Valid { get; set; } - [JsonPropertyName("parameters")] - public string Parameters { get; set; } + [JsonIgnore] + public bool IsFull => Spawned.Count >= SpawnedMaxCount; - [JsonPropertyName("properties")] - public string Properties { get; set; } - - [JsonPropertyName("maxCount")] - public int SpawnedMaxCount { get; set; } - - [JsonPropertyName("probability")] - public int SpawnedProbability { get; set; } - - [JsonIgnore] - public EntryFlags Valid { get; set; } - - [JsonIgnore] - public List Spawned { get; } - - [JsonIgnore] - public bool IsFull => Spawned.Count >= SpawnedMaxCount; - - public void Serialize(IGenericWriter writer) + public void Defrag(BaseSpawner parent) + { + for (var i = 0; i < Spawned.Count; ++i) { - writer.Write(0); // version + var spawned = Spawned[i]; - writer.Write(SpawnedName); - writer.Write(SpawnedProbability); - writer.Write(SpawnedMaxCount); - - writer.Write(Properties); - writer.Write(Parameters); - - writer.Write(Spawned.Count); - - for (var i = 0; i < Spawned.Count; ++i) + if (parent.OnDefragSpawn(spawned, false)) { - object o = Spawned[i]; - - if (o is Item item) - { - writer.Write(item); - } - else if (o is Mobile mobile) - { - writer.Write(mobile); - } - else - { - writer.Write(Serial.MinusOne); - } - } - } - - public void Defrag(BaseSpawner parent) - { - for (var i = 0; i < Spawned.Count; ++i) - { - var spawned = Spawned[i]; - - if (parent.OnDefragSpawn(spawned, false)) - { - Spawned.RemoveAt(i--); - } + Spawned.RemoveAt(i--); } } } diff --git a/Projects/UOContent/Migrations/Server.Engines.Spawners.BaseSpawner.v10.json b/Projects/UOContent/Migrations/Server.Engines.Spawners.BaseSpawner.v10.json new file mode 100644 index 000000000..f65582e95 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Spawners.BaseSpawner.v10.json @@ -0,0 +1,100 @@ +{ + "version": 10, + "type": "Server.Engines.Spawners.BaseSpawner", + "properties": [ + { + "name": "Guid", + "type": "System.Guid", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "ReturnOnDeactivate", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Entries", + "type": "System.Collections.Generic.List\u003CServer.Engines.Spawners.SpawnerEntry\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.Engines.Spawners.SpawnerEntry", + "RawSerializableMigrationRule", + "DeserializationRequiresParent" + ] + }, + { + "name": "WalkingRange", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "WayPoint", + "type": "Server.Items.WayPoint", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Group", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "MinDelay", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "MaxDelay", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Count", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Team", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "HomeRange", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Running", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.Spawners.ProximitySpawner.v0.json b/Projects/UOContent/Migrations/Server.Engines.Spawners.ProximitySpawner.v0.json new file mode 100644 index 000000000..d0bcd4ab1 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Spawners.ProximitySpawner.v0.json @@ -0,0 +1,30 @@ +{ + "version": 0, + "type": "Server.Engines.Spawners.ProximitySpawner", + "properties": [ + { + "name": "TriggerRange", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SpawnMessage", + "type": "Server.TextDefinition", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "TextDefinition" + ] + }, + { + "name": "InstantFlag", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.Spawners.RegionSpawner.v0.json b/Projects/UOContent/Migrations/Server.Engines.Spawners.RegionSpawner.v0.json new file mode 100644 index 000000000..5388f08ff --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Spawners.RegionSpawner.v0.json @@ -0,0 +1,14 @@ +{ + "version": 0, + "type": "Server.Engines.Spawners.RegionSpawner", + "properties": [ + { + "name": "SpawnRegionName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.Spawners.Spawner.v0.json b/Projects/UOContent/Migrations/Server.Engines.Spawners.Spawner.v0.json new file mode 100644 index 000000000..873e630ff --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Spawners.Spawner.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Engines.Spawners.Spawner" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.Spawners.SpawnerEntry.v1.json b/Projects/UOContent/Migrations/Server.Engines.Spawners.SpawnerEntry.v1.json new file mode 100644 index 000000000..550450b00 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Spawners.SpawnerEntry.v1.json @@ -0,0 +1,55 @@ +{ + "version": 1, + "type": "Server.Engines.Spawners.SpawnerEntry", + "properties": [ + { + "name": "SpawnedName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SpawnedProbability", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SpawnedMaxCount", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Properties", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Parameters", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Spawned", + "type": "System.Collections.Generic.List\u003CServer.ISpawnable\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.ISpawnable", + "SerializableInterfaceMigrationRule" + ] + } + ] +} \ No newline at end of file