fix: Fixes spawners, adds convertpremiumspawners, exportspawners, and replaces with neruns distro (#751)
### Additions * Adds `[exportspawners <relative json file path to distro>` * If no path is provided, it will be saved to the `Data\Spawns` folder with the current timestamp as the file name. * Supports global, facet, region, multi, and area * Fixes client disconnect for bad spawner generation command. * Moves spawner commands to their own folder. * Adds GUIDs for spawners. This allows for easy replacement during import to reduce duplication. * Allows exporting the name of the spawner. * Fixes globbing when using [generatespawners * Adds [convertpremiumspawners to convert Premium Spawners (from Neruns distro) Possibly in .NET 6 serializing JSON will be more performant using JSON nodes. ### Screenshots   
This commit is contained in:
parent
dbd4d6f13f
commit
0d1fffd9fc
211 changed files with 200435 additions and 91312 deletions
|
|
@ -55,6 +55,7 @@ namespace Server
|
|||
// TODO: Add SpawnMap and change Spawner.Map to use it
|
||||
public interface ISpawner : IEntity
|
||||
{
|
||||
Guid Guid { get; }
|
||||
bool UnlinkOnTaming { get; }
|
||||
Point3D HomeLocation { get; }
|
||||
int HomeRange { get; }
|
||||
|
|
|
|||
38
Projects/Server/Json/Converters/GuidConverter.cs
Normal file
38
Projects/Server/Json/Converters/GuidConverter.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: GuidConverter.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server.Json
|
||||
{
|
||||
public class GuidConverter : JsonConverter<Guid>
|
||||
{
|
||||
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (Guid.TryParse(reader.GetString()!, out var guid))
|
||||
{
|
||||
return guid;
|
||||
}
|
||||
|
||||
throw new JsonException("Guid must be in the correct format");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
30
Projects/Server/Json/Converters/GuidConverterFactory.cs
Normal file
30
Projects/Server/Json/Converters/GuidConverterFactory.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: GuidConverterFactory.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Server.Json
|
||||
{
|
||||
public class GuidConverterFactory : JsonConverterFactory
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Guid);
|
||||
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
|
||||
new GuidConverter();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,15 +22,28 @@ namespace Server.Json
|
|||
{
|
||||
public class DynamicJson
|
||||
{
|
||||
public static DynamicJson Create(Type type) => new()
|
||||
{
|
||||
Type = type.Name,
|
||||
Data = new Dictionary<string, JsonElement>()
|
||||
};
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, JsonElement> data { get; set; }
|
||||
public Dictionary<string, JsonElement> Data { get; set; }
|
||||
|
||||
// TODO: Use JSON Node in .NET 6
|
||||
public void SetProperty<T>(string key, JsonSerializerOptions options, T value)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value, options));
|
||||
Data[key] = doc.RootElement.Clone();
|
||||
}
|
||||
|
||||
public bool GetProperty<T>(string key, JsonSerializerOptions options, out T t)
|
||||
{
|
||||
if (data.TryGetValue(key, out var el))
|
||||
if (Data.TryGetValue(key, out var el))
|
||||
{
|
||||
t = el.ToObject<T>(options);
|
||||
return true;
|
||||
|
|
@ -42,7 +55,7 @@ namespace Server.Json
|
|||
|
||||
public bool GetEnumProperty<T>(string key, JsonSerializerOptions options, out T t) where T : struct, Enum
|
||||
{
|
||||
if (data.TryGetValue(key, out var el))
|
||||
if (Data.TryGetValue(key, out var el))
|
||||
{
|
||||
return Enum.TryParse(el.ToObject<string>(options), out t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ namespace Server.Json
|
|||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
options.Converters.Add(new GuidConverterFactory());
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new MapConverterFactory());
|
||||
options.Converters.Add(new Point3DConverterFactory());
|
||||
|
|
|
|||
|
|
@ -583,6 +583,46 @@ namespace Server
|
|||
return count;
|
||||
}
|
||||
|
||||
public List<Item> GetItems()
|
||||
{
|
||||
var list = new List<Item>();
|
||||
|
||||
for (var i = 0; i < Sectors?.Length; i++)
|
||||
{
|
||||
var sector = Sectors[i];
|
||||
|
||||
foreach (var item in sector.Items)
|
||||
{
|
||||
if (Find(item.Location, item.Map).IsPartOf(this))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public int GetItemCount()
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < Sectors?.Length; i++)
|
||||
{
|
||||
var sector = Sectors[i];
|
||||
|
||||
foreach (var item in sector.Items)
|
||||
{
|
||||
if (Find(item.Location, item.Map).IsPartOf(this))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public override string ToString() => Name ?? GetType().Name;
|
||||
|
||||
public virtual void OnRegister()
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@ namespace Server
|
|||
|
||||
return default;
|
||||
}
|
||||
Guid ReadGuid()
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[16];
|
||||
Read(bytes);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
long Seek(long offset, SeekOrigin origin);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,6 +155,12 @@ namespace Server
|
|||
}
|
||||
}
|
||||
}
|
||||
void Write(Guid guid)
|
||||
{
|
||||
Span<byte> stack = stackalloc byte[16];
|
||||
guid.TryWriteBytes(stack);
|
||||
Write(stack);
|
||||
}
|
||||
|
||||
long Seek(long offset, SeekOrigin origin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Server.Commands.Generic
|
|||
All = Single | Global | Online | Multi | Area | Self | Region | Contained | IPAddress,
|
||||
AllMobiles = All & ~Contained,
|
||||
AllNPCs = All & ~(IPAddress | Online | Self | Contained),
|
||||
AllItems = All & ~(IPAddress | Online | Self | Region),
|
||||
AllItems = All & ~(IPAddress | Online | Self),
|
||||
|
||||
Simple = Single | Multi,
|
||||
Complex = Global | Online | Area | Region | Contained | IPAddress
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
var ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out var _, out var mobiles))
|
||||
if (!CheckObjectTypes(from, command, ext, out var items, out var mobiles))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -35,21 +35,22 @@ namespace Server.Commands.Generic
|
|||
{
|
||||
foreach (var mob in reg.GetMobiles())
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, mob))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext.IsValid(mob))
|
||||
if (BaseCommand.IsAccessible(from, mob) && ext.IsValid(mob))
|
||||
{
|
||||
list.Add(mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (items)
|
||||
{
|
||||
command.LogFailure("This command does not support items.");
|
||||
return;
|
||||
foreach (var item in reg.GetItems())
|
||||
{
|
||||
if (BaseCommand.IsAccessible(from, item) && ext.IsValid(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext.Filter(list);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ namespace Server.Engines.Spawners
|
|||
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;
|
||||
|
|
@ -62,6 +64,7 @@ namespace Server.Engines.Spawners
|
|||
params string[] spawnedNames
|
||||
) : base(0x1f13)
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
InitSpawn(amount, minDelay, maxDelay, team, homeRange);
|
||||
for (var i = 0; i < spawnedNames.Length; i++)
|
||||
{
|
||||
|
|
@ -71,6 +74,16 @@ namespace Server.Engines.Spawners
|
|||
|
||||
public BaseSpawner(DynamicJson json, JsonSerializerOptions options) : base(0x1f13)
|
||||
{
|
||||
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);
|
||||
|
|
@ -102,6 +115,13 @@ namespace Server.Engines.Spawners
|
|||
|
||||
public Dictionary<ISpawnable, SpawnerEntry> Spawned { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.Developer)]
|
||||
public Guid Guid
|
||||
{
|
||||
get => _guid;
|
||||
set => _guid = value;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.Developer)]
|
||||
public int Count
|
||||
{
|
||||
|
|
@ -261,6 +281,22 @@ namespace Server.Engines.Spawners
|
|||
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)
|
||||
|
|
@ -872,7 +908,9 @@ namespace Server.Engines.Spawners
|
|||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(8); // version
|
||||
writer.Write(9); // version
|
||||
|
||||
writer.Write(_guid);
|
||||
|
||||
writer.Write(ReturnOnDeactivate);
|
||||
|
||||
|
|
@ -917,6 +955,11 @@ namespace Server.Engines.Spawners
|
|||
|
||||
switch (version)
|
||||
{
|
||||
case 9:
|
||||
{
|
||||
_guid = reader.ReadGuid();
|
||||
goto case 8;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
ReturnOnDeactivate = reader.ReadBool();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,261 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ConvertPremiumSpawners.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
||||
using Server.Json;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.Spawners
|
||||
{
|
||||
public static class ConvertPremiumSpawners
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("ConvertPremiumSpawners", AccessLevel.Developer, ConvertPremiumSpawners_OnCommand);
|
||||
}
|
||||
|
||||
private static void ConvertPremiumSpawners_OnCommand(CommandEventArgs args)
|
||||
{
|
||||
var from = args.Mobile;
|
||||
|
||||
if (args.Arguments.Length != 2)
|
||||
{
|
||||
from.SendMessage("Usage: [ConvertPremiumSpawners <relative search pattern to distribution> <output directory relative to distribution>");
|
||||
return;
|
||||
}
|
||||
|
||||
var inputDi = new DirectoryInfo(Core.BaseDirectory);
|
||||
|
||||
var patternMatches = new Matcher()
|
||||
.AddInclude(args.Arguments[0])
|
||||
.Execute(new DirectoryInfoWrapper(inputDi))
|
||||
.Files;
|
||||
|
||||
List<FileInfo> files = new List<FileInfo>();
|
||||
foreach (var match in patternMatches)
|
||||
{
|
||||
files.Add(new FileInfo(match.Path));
|
||||
}
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
from.SendMessage("ConvertPremiumSpawners: No files found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var inputDir = Path.Combine(Core.BaseDirectory, args.Arguments[0]);
|
||||
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 = files[i];
|
||||
from.SendMessage("ConvertPremiumSpawners: Converting spawners for {0}...", file.Name);
|
||||
|
||||
NetState.FlushAll();
|
||||
|
||||
try
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(inputDir, file.DirectoryName!);
|
||||
var fullOutputDir = Path.Combine(outputDir, relativePath);
|
||||
AssemblyHandler.EnsureDirectory(fullOutputDir);
|
||||
ParsePremiumSpawnerFile(file, fullOutputDir, options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
from.SendMessage(
|
||||
"ConvertPremiumSpawners: Exception parsing {0}, file may not be in the correct format.",
|
||||
file.FullName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParsePremiumSpawnerFile(FileInfo file, string outputDirectory, JsonSerializerOptions options)
|
||||
{
|
||||
var lines = File.ReadAllLines(file.FullName);
|
||||
|
||||
TimeSpan minTime = TimeSpan.MinValue;
|
||||
TimeSpan maxTime = TimeSpan.MinValue;
|
||||
int mapId = -1;
|
||||
string spawnerId = null;
|
||||
|
||||
var json = new List<DynamicJson>();
|
||||
|
||||
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 dynamicJson = new DynamicJson
|
||||
{
|
||||
Type = "Spawner",
|
||||
Data = new Dictionary<string, JsonElement>()
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var outputFile = Path.Combine(outputDirectory, $"{file.Name[..^file.Extension.Length]}.json");
|
||||
Console.WriteLine("Writing to: {0}", outputFile);
|
||||
JsonConfig.Serialize(outputFile, json, options);
|
||||
}
|
||||
|
||||
private static List<Spawner> ParsePremiumSpawner(
|
||||
string[] parts,
|
||||
string spawnerIdOverride,
|
||||
int mapIdOverride,
|
||||
TimeSpan minTimeOverride,
|
||||
TimeSpan maxTimeOverride
|
||||
)
|
||||
{
|
||||
var spawnersList = new List<Spawner>();
|
||||
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()
|
||||
};
|
||||
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
spawner.Entries.AddRange(CreateSpawnerEntries(parts[i + 1], int.Parse(parts[i + 15])));
|
||||
}
|
||||
|
||||
if (spawner.Entries.Count == 0)
|
||||
{
|
||||
spawner.Delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
var totalCount = 0;
|
||||
foreach (var entry in spawner.Entries)
|
||||
{
|
||||
totalCount += entry.SpawnedMaxCount;
|
||||
}
|
||||
spawner.Count = totalCount;
|
||||
|
||||
spawnersList.Add(spawner);
|
||||
}
|
||||
|
||||
return spawnersList;
|
||||
}
|
||||
|
||||
private static List<SpawnerEntry> CreateSpawnerEntries(string typeList, int maxCount)
|
||||
{
|
||||
var list = new List<SpawnerEntry>();
|
||||
if (string.IsNullOrWhiteSpace(typeList))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
foreach (var spawnType in typeList.Split(':'))
|
||||
{
|
||||
var actualType = AssemblyHandler.FindTypeByName(ConvertType(spawnType))?.Name ?? spawnType;
|
||||
list.Add(new SpawnerEntry(actualType, 100, maxCount));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static TimeSpan GetTimeSpan(string time)
|
||||
{
|
||||
if (string.IsNullOrEmpty(time))
|
||||
{
|
||||
return TimeSpan.MinValue;
|
||||
}
|
||||
|
||||
time = time.ToLowerInvariant();
|
||||
|
||||
return time[^1] switch
|
||||
{
|
||||
'h' => TimeSpan.FromHours(double.Parse(time[..^1])),
|
||||
'm' => TimeSpan.FromMinutes(double.Parse(time[..^1])),
|
||||
's' => TimeSpan.FromSeconds(double.Parse(time[..^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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2021 - ModernUO Development Team *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: EditSpawnCommand.cs *
|
||||
* File: EditSpawnerCommand.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 *
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ExportSpawnersCommand.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Json;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.Spawners
|
||||
{
|
||||
public class ExportSpawnersCommand : BaseCommand
|
||||
{
|
||||
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 the spawners to the a file";
|
||||
ListOptimized = true;
|
||||
}
|
||||
|
||||
public override void ExecuteList(CommandEventArgs e, List<object> list)
|
||||
{
|
||||
var path = e.Arguments.Length == 0 ? null : e.Arguments[0].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
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);
|
||||
AssemblyHandler.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<DynamicJson>(list.Count);
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i] is BaseSpawner spawner)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: GenerateSpawnersCommand.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
||||
using Server.Collections;
|
||||
using Server.Json;
|
||||
using Server.Logging;
|
||||
using Server.Network;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Engines.Spawners
|
||||
{
|
||||
public static class GenerateSpawnersCommand
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(GenerateSpawnersCommand));
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand);
|
||||
}
|
||||
|
||||
private static void GenerateSpawners_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
if (e.Arguments.Length == 0)
|
||||
{
|
||||
from.SendMessage("Usage: [GenerateSpawners <relative search pattern to distribution>");
|
||||
return;
|
||||
}
|
||||
|
||||
var di = new DirectoryInfo(Core.BaseDirectory);
|
||||
|
||||
var patternMatches = new Matcher()
|
||||
.AddInclude(e.Arguments[0])
|
||||
.Execute(new DirectoryInfoWrapper(di))
|
||||
.Files;
|
||||
|
||||
List<FileInfo> files = new List<FileInfo>();
|
||||
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<Guid, ISpawner>();
|
||||
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 {0}...", file.Name);
|
||||
logger.Information($"{from} is generating spawners from {file.FullName}");
|
||||
|
||||
NetState.FlushAll();
|
||||
|
||||
try
|
||||
{
|
||||
var spawners = JsonConfig.Deserialize<List<DynamicJson>>(file.FullName);
|
||||
ParseSpawnerList(spawners, options, allSpawners, out var generated, out var failed);
|
||||
totalGenerated += generated;
|
||||
totalFailures += failed;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
from.SendMessage(
|
||||
"GenerateSpawners: Exception parsing {0}, file may not be in the correct format.",
|
||||
file.FullName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
|
||||
logger.Information("Generated {0} spawners ({1:F2} seconds, {2} failures)");
|
||||
from.SendMessage(
|
||||
"GenerateSpawners: Generated {0} spawners ({1:F2} seconds, {2} failures)",
|
||||
totalGenerated,
|
||||
watch.Elapsed.TotalSeconds,
|
||||
totalFailures
|
||||
);
|
||||
}
|
||||
|
||||
private static void ParseSpawnerList(
|
||||
List<DynamicJson> spawners,
|
||||
JsonSerializerOptions options,
|
||||
Dictionary<Guid, ISpawner> allSpawners,
|
||||
out int totalGenerated,
|
||||
out int failureCount
|
||||
)
|
||||
{
|
||||
failureCount = 0;
|
||||
totalGenerated = 0;
|
||||
|
||||
using var queue = PooledRefQueue<Item>.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?
|
||||
var eable = map.GetItemsInRange<BaseSpawner>(location, 0);
|
||||
foreach (var spawner in eable)
|
||||
{
|
||||
if (spawner.GetType() == type)
|
||||
{
|
||||
queue.Enqueue(spawner);
|
||||
allSpawners.Remove(spawner.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
queue.Dequeue().Delete();
|
||||
}
|
||||
|
||||
eable.Free();
|
||||
|
||||
try
|
||||
{
|
||||
var spawner = type.CreateInstance<ISpawner>(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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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($"{message}\n{ex}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2021 - ModernUO Development Team *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: RespawnCommand.cs *
|
||||
* *
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Server.Json;
|
||||
using Server.Network;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Engines.Spawners
|
||||
{
|
||||
public static class GenerateSpawners
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand);
|
||||
}
|
||||
|
||||
private static void GenerateSpawners_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
if (e.Arguments.Length == 0)
|
||||
{
|
||||
from.SendMessage("Usage: [GenerateSpawners <path|search pattern>");
|
||||
return;
|
||||
}
|
||||
|
||||
var di = new DirectoryInfo(Core.BaseDirectory);
|
||||
|
||||
var files = di.GetFiles(e.Arguments[0], SearchOption.AllDirectories);
|
||||
|
||||
if (files.Length == 0)
|
||||
{
|
||||
from.SendMessage("GenerateSpawners: No files found matching the pattern");
|
||||
return;
|
||||
}
|
||||
|
||||
var options = JsonConfig.GetOptions(new TextDefinitionConverterFactory());
|
||||
|
||||
for (var i = 0; i < files.Length; i++)
|
||||
{
|
||||
var file = files[i];
|
||||
from.SendMessage("GenerateSpawners: Generating spawners for {0}...", file.Name);
|
||||
|
||||
NetState.FlushAll();
|
||||
|
||||
try
|
||||
{
|
||||
var spawners = JsonConfig.Deserialize<List<DynamicJson>>(file.FullName);
|
||||
ParseSpawnerList(from, spawners, options);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
from.SendMessage(
|
||||
"GenerateSpawners: Exception parsing {0}, file may not be in the correct format.",
|
||||
file.FullName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseSpawnerList(Mobile from, List<DynamicJson> spawners, JsonSerializerOptions options)
|
||||
{
|
||||
var watch = Stopwatch.StartNew();
|
||||
var failures = new List<string>();
|
||||
var count = 0;
|
||||
|
||||
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))
|
||||
{
|
||||
var failure = $"GenerateSpawners: Invalid spawner type {json.Type ?? "(-null-)"} ({i})";
|
||||
if (!failures.Contains(failure))
|
||||
{
|
||||
failures.Add(failure);
|
||||
from.SendMessage(failure);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
json.GetProperty("location", options, out Point3D location);
|
||||
json.GetProperty("map", options, out Map map);
|
||||
|
||||
var eable = map.GetItemsInRange<BaseSpawner>(location, 0);
|
||||
|
||||
if (eable.Any(sp => sp.GetType() == type))
|
||||
{
|
||||
eable.Free();
|
||||
continue;
|
||||
}
|
||||
|
||||
eable.Free();
|
||||
|
||||
try
|
||||
{
|
||||
var spawner = type.CreateInstance<ISpawner>(json, options);
|
||||
|
||||
spawner!.MoveToWorld(location, map);
|
||||
spawner!.Respawn();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var failure = $"GenerateSpawners: Spawner {type} failed to construct";
|
||||
if (!failures.Contains(failure))
|
||||
{
|
||||
failures.Add(failure);
|
||||
from.SendMessage(failure);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
from.SendMessage(
|
||||
"GenerateSpawners: Generated {0} spawners ({1:F2} seconds, {2} failures)",
|
||||
count,
|
||||
watch.Elapsed.TotalSeconds,
|
||||
failures.Count
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,18 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ProximitySpawner.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Server.Json;
|
||||
|
|
@ -86,6 +101,13 @@ namespace Server.Engines.Spawners
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2021 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: RegionSpawner.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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Server.Json;
|
||||
|
|
@ -68,6 +83,11 @@ namespace Server.Engines.Spawners
|
|||
}
|
||||
}
|
||||
|
||||
public override void ToJson(DynamicJson json, JsonSerializerOptions options)
|
||||
{
|
||||
json.SetProperty("region", options, SpawnRegion.Name);
|
||||
}
|
||||
|
||||
public override void GetSpawnerProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetSpawnerProperties(list);
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ namespace Server.Engines.Spawners
|
|||
c.Delete();
|
||||
remove = true;
|
||||
}
|
||||
|
||||
|
||||
return base.OnDefragSpawn(entry, spawned, remove);
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -49,20 +49,20 @@ namespace Server.Engines.Spawners
|
|||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("probability")]
|
||||
public int SpawnedProbability { get; set; }
|
||||
|
||||
[JsonPropertyName("maxCount")]
|
||||
public int SpawnedMaxCount { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string SpawnedName { get; set; }
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public string Parameters { get; set; }
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public string Properties { get; set; }
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public string Parameters { get; set; }
|
||||
[JsonPropertyName("maxCount")]
|
||||
public int SpawnedMaxCount { get; set; }
|
||||
|
||||
[JsonPropertyName("probability")]
|
||||
public int SpawnedProbability { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public EntryFlags Valid { get; set; }
|
||||
|
|
@ -70,6 +70,7 @@ namespace Server.Engines.Spawners
|
|||
[JsonIgnore]
|
||||
public List<ISpawnable> Spawned { get; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsFull => Spawned.Count >= SpawnedMaxCount;
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
<IncludeInPackage>false</IncludeInPackage>
|
||||
</ProjectReference>
|
||||
<PackageReference Include="MailKit" Version="2.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.0.2" />
|
||||
<PackageReference Include="Zlib.Bindings" Version="1.5.0" />
|
||||
<PackageReference Include="Argon2.Bindings" Version="1.9.1" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue