fix: Codegens spawners (#1678)
This commit is contained in:
parent
48e47f9352
commit
5ada7a4e50
19 changed files with 1494 additions and 1604 deletions
|
|
@ -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 <relative search pattern to distribution> <output directory relative to distribution>")]
|
||||
[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 <relative search pattern to distribution> <output directory relative to distribution>");
|
||||
return;
|
||||
}
|
||||
|
||||
[Usage("ConvertPremiumSpawners <relative search pattern to distribution> <output directory relative to distribution>")]
|
||||
[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 <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, 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<DynamicJson>();
|
||||
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<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 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<string, JsonElement>()
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
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<SpawnerEntry> CreateSpawnerEntries(string typeList, int maxCount)
|
||||
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 ? [Map.Felucca, Map.Trammel] : [Map.Maps[mapId - 1]];
|
||||
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var list = new List<SpawnerEntry>();
|
||||
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<SpawnerEntry> CreateSpawnerEntries(BaseSpawner spawner, string typeList, int maxCount)
|
||||
{
|
||||
var list = new List<SpawnerEntry>();
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <type> <arguments> set <properties> where <properties>";
|
||||
Description = "Modifies spawners arguments and properties for the given type";
|
||||
ListOptimized = true;
|
||||
}
|
||||
|
||||
public override void ExecuteList(CommandEventArgs e, List<object> 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 <type> <arguments> set <properties> where <properties>";
|
||||
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<object> 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<char> 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<char> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 <relative search pattern to distribution>")]
|
||||
[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 <relative search pattern to distribution>");
|
||||
return;
|
||||
}
|
||||
|
||||
[Usage("GenerateSpawners <relative search pattern to distribution>")]
|
||||
[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<FileInfo> files = [];
|
||||
foreach (var match in patternMatches)
|
||||
{
|
||||
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 {file.Name}...");
|
||||
logger.Information("{User} is generating spawners from {File}", 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 {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<DynamicJson> spawners,
|
||||
JsonSerializerOptions options,
|
||||
Dictionary<Guid, ISpawner> 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<Item>.Create();
|
||||
for (var i = 0; i < spawners.Count; i++)
|
||||
var watch = Stopwatch.StartNew();
|
||||
|
||||
var allSpawners = new Dictionary<Guid, ISpawner>();
|
||||
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<BaseSpawner>(location))
|
||||
{
|
||||
if (spawner.GetType() == type)
|
||||
{
|
||||
queue.Enqueue(spawner);
|
||||
allSpawners.Remove(spawner.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
queue.Dequeue().Delete();
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
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<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 {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<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?
|
||||
foreach (var spawner in map.GetItemsAt<BaseSpawner>(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<ISpawner>(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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<object> 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<object> 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.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<object> 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<object> list)
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private readonly List<object> _list;
|
||||
|
||||
public InternalTarget(List<object> 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<object> _list;
|
||||
|
||||
public InternalTarget(List<object> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue