From a9a2a899083836802226b0a87e4d3197d9ecd4a8 Mon Sep 17 00:00:00 2001 From: mdodkins Date: Tue, 1 Aug 2023 04:46:49 +0100 Subject: [PATCH] feat: Customize expansion and set maps on first boot (#1425) --- .gitignore | 2 + .../Data/{expansion.json => expansions.json} | 142 +++++++++++++++--- .../Tests/Buffers/ValueStringBuilderTests.cs | 2 +- .../Tests/Maps/MapSelectionTests.cs | 50 ++++++ .../Packets/Outgoing/MobilePacketTests.cs | 4 +- Projects/Server/Buffers/ValueStringBuilder.cs | 4 +- Projects/Server/Client/ClientVersion.cs | 2 +- .../Server/Collections/ArrayEnumerator.cs | 2 +- .../ExpansionConfigurationPrompts.cs | 118 +++++++++++++++ .../Configuration/ServerConfiguration.cs | 79 ++++++---- .../ServerConfigurationPrompts.cs | 49 ------ .../Server/Configuration/ServerSettings.cs | 8 +- Projects/Server/ExpansionInfo.cs | 129 ++++++++-------- .../Converters/TextDefinitionConverter.cs | 39 +++-- .../TextDefinitionConverterFactory.cs | 15 +- .../Server/Localization/LocalizationEntry.cs | 1 + Projects/Server/Main.cs | 3 +- Projects/Server/Maps/Map.cs | 1 - Projects/Server/Maps/MapSelection.cs | 113 ++++++++++++++ Projects/Server/Mobiles/Mobile.cs | 2 +- .../NetState/NetState.ClientVersion.cs | 2 +- .../Network/Packets/OutgoingMobilePackets.cs | 2 +- Projects/Server/Regions/Region.cs | 1 - Projects/Server/Skills.cs | 2 +- Projects/Server/Text/TextEncoding.cs | 1 - .../UOContent.Tests/Fixtures/ServerFixture.cs | 1 - Projects/UOContent/Accounting/Account.cs | 2 +- .../Implementors/BaseCommandImplementor.cs | 2 +- Projects/UOContent/Commands/Properties.cs | 2 +- Projects/UOContent/Compression/TarArchive.cs | 2 +- .../Engines/Ethics/Evil/Powers/UnholySense.cs | 2 +- .../Engines/Ethics/Hero/Powers/HolySense.cs | 2 +- .../Engines/Spawners/SpawnPropsGump.cs | 2 +- .../Engines/Spawners/SpawnerEntry.cs | 1 - Projects/UOContent/Gumps/AdminGump.cs | 45 +++--- Projects/UOContent/Items/Books/BaseBook.cs | 2 +- .../UOContent/Items/Misc/PublicMoongate.cs | 45 ++++-- Projects/UOContent/Items/Misc/Teleporter.cs | 2 +- .../Special/House Raffle/HouseRaffleStone.cs | 2 +- Projects/UOContent/Misc/CharacterCreation.cs | 69 ++++++++- Projects/UOContent/Misc/ClientVerification.cs | 2 +- Projects/UOContent/Misc/InhumanSpeech.cs | 2 +- Projects/UOContent/Misc/Titles.cs | 3 +- .../UOContent/Multis/ComponentVerification.cs | 2 +- Projects/UOContent/Skills/ForensicEval.cs | 2 +- 45 files changed, 698 insertions(+), 267 deletions(-) rename Distribution/Data/{expansion.json => expansions.json} (86%) create mode 100644 Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs create mode 100644 Projects/Server/Configuration/ExpansionConfigurationPrompts.cs create mode 100644 Projects/Server/Maps/MapSelection.cs diff --git a/.gitignore b/.gitignore index 13a182b470..24d5cd8dd2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /Distribution/bsdtar /Distribution/Configuration/antimacro.json /Distribution/Configuration/assistants.json +/Distribution/Configuration/expansion.json /Distribution/Configuration/modernuo.json /Distribution/Configuration/email-settings.json /Distribution/Configuration/throttles.json @@ -25,6 +26,7 @@ /Projects/*/bin /Projects/*/Generated +*.swp *.log *.user /.idea diff --git a/Distribution/Data/expansion.json b/Distribution/Data/expansions.json similarity index 86% rename from Distribution/Data/expansion.json rename to Distribution/Data/expansions.json index c269c8f470..0496cfe30e 100644 --- a/Distribution/Data/expansion.json +++ b/Distribution/Data/expansions.json @@ -1,8 +1,9 @@ [ { + "Id": 0, "Name": "None", - "ClientVersion": null, - "ClientFlags": null, + "RequiredClient": null, + "ClientFlags": "None", "FeatureFlags": { "T2A": false, "UOR": false, @@ -29,6 +30,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": false, + "Ilshenar": false, + "Malas": false, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -64,8 +73,9 @@ "MobileStatusVersion": 3 }, { + "Id": 1, "Name": "The Second Age", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Felucca", "FeatureFlags": { "T2A": true, @@ -93,6 +103,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": false, + "Malas": false, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -128,8 +146,9 @@ "MobileStatusVersion": 3 }, { + "Id": 2, "Name": "Renaissance", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Trammel", "FeatureFlags": { "T2A": true, @@ -157,6 +176,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": false, + "Malas": false, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -192,8 +219,9 @@ "MobileStatusVersion": 3 }, { + "Id": 3, "Name": "Third Dawn", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Ilshenar", "FeatureFlags": { "T2A": true, @@ -221,6 +249,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": false, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -256,8 +292,9 @@ "MobileStatusVersion": 3 }, { + "Id": 4, "Name": "Blackthorn's Revenge", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Ilshenar", "FeatureFlags": { "T2A": true, @@ -285,6 +322,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": false, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -320,8 +365,9 @@ "MobileStatusVersion": 3 }, { + "Id": 5, "Name": "Age of Shadows", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Malas", "FeatureFlags": { "T2A": true, @@ -349,6 +395,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": false, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -384,8 +438,9 @@ "MobileStatusVersion": 6 }, { + "Id": 6, "Name": "Samurai Empire", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "Tokuno", "FeatureFlags": { "T2A": true, @@ -413,6 +468,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -448,9 +511,10 @@ "MobileStatusVersion": 6 }, { + "Id": 7, "Name": "Mondain's Legacy", - "ClientVersion": "5.0.0a", - "ClientFlags": null, + "RequiredClient": "5.0.0a", + "ClientFlags": "None", "FeatureFlags": { "T2A": true, "UOR": true, @@ -477,6 +541,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": false + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -512,8 +584,9 @@ "MobileStatusVersion": 6 }, { + "Id": 8, "Name": "Stygian Abyss", - "ClientVersion": null, + "RequiredClient": null, "ClientFlags": "TerMur", "FeatureFlags": { "T2A": true, @@ -541,6 +614,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": true + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -576,9 +657,10 @@ "MobileStatusVersion": 6 }, { + "Id": 9, "Name": "High Seas", - "ClientVersion": "7.0.9.0", - "ClientFlags": null, + "RequiredClient": "7.0.9.0", + "ClientFlags": "None", "FeatureFlags": { "T2A": true, "UOR": true, @@ -605,6 +687,14 @@ "TOL": false, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": true + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -640,9 +730,10 @@ "MobileStatusVersion": 6 }, { + "Id": 10, "Name": "Time of Legends", - "ClientVersion": "7.0.45.65", - "ClientFlags": null, + "RequiredClient": "7.0.45.65", + "ClientFlags": "None", "FeatureFlags": { "T2A": true, "UOR": true, @@ -669,6 +760,14 @@ "TOL": true, "EJ": false }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": true + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, @@ -704,9 +803,10 @@ "MobileStatusVersion": 6 }, { + "Id": 11, "Name": "Endless Journey", - "ClientVersion": "7.0.61.0", - "ClientFlags": null, + "RequiredClient": "7.0.61.0", + "ClientFlags": "None", "FeatureFlags": { "T2A": true, "UOR": true, @@ -733,6 +833,14 @@ "TOL": true, "EJ": true }, + "MapSelectionFlags": { + "Felucca": true, + "Trammel": true, + "Ilshenar": true, + "Malas": true, + "Tokuno": true, + "TerMer": true + }, "CharacterListFlags": { "Unk1": false, "OverwriteConfigButton": false, diff --git a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs index 7e5c17795d..33d3c3eb94 100644 --- a/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs +++ b/Projects/Server.Tests/Tests/Buffers/ValueStringBuilderTests.cs @@ -1,4 +1,4 @@ -using Server.Buffers; +using Server.Text; using Xunit; namespace Server.Tests.Buffers; diff --git a/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs b/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs new file mode 100644 index 0000000000..6c34c584ab --- /dev/null +++ b/Projects/Server.Tests/Tests/Maps/MapSelectionTests.cs @@ -0,0 +1,50 @@ +using Xunit; +using Server.Maps; +using Server.Json; +using System.Text.Json.Serialization; +using System.Text.Json; + +namespace Server.Tests.Tests.Maps +{ + public class MapSelectionTests + { + [Theory] + [InlineData(MapSelectionFlags.Felucca, "Felucca")] + [InlineData(MapSelectionFlags.Felucca | MapSelectionFlags.Trammel, "Felucca, Trammel")] + public void TestCommaSeparatedList(MapSelectionFlags flags, string expected) + { + Assert.Equal(expected, flags.ToCommaDelimitedString()); + } + + [Fact] + public void TestFormatFeluccaOnlyMapSelection() + { + const MapSelectionFlags flags = MapSelectionFlags.Felucca; + + // When, Then + Assert.Equal("Felucca", flags.ToCommaDelimitedString()); + } + + public class TestMapConfig + { + [JsonConverter(typeof(FlagsConverter))] + public MapSelectionFlags TestMapFlags { get; set; } + } + + [Fact] + public void TestSerializeAndDeserializeMapSelectionFlags() + { + TestMapConfig mapConfig = new() + { + TestMapFlags = MapSelectionFlags.Felucca | MapSelectionFlags.Ilshenar, + }; + + // When + string serialized = JsonConfig.Serialize(mapConfig); + TestMapConfig deserialized = JsonSerializer.Deserialize(serialized, JsonConfig.GetOptions()); + + // Then + Assert.Equal(mapConfig.TestMapFlags, deserialized.TestMapFlags); + } + } +} diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs index e01150cb03..ebac1538e5 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs @@ -243,11 +243,11 @@ public void TestMobileStatusExtendedSelf( int mobileStatusVersion ) { - var expansionInfo = ExpansionInfo.GetInfo(Core.Expansion); + var expansionInfo = ExpansionInfo.CoreExpansion; var oldExpansion = Core.Expansion; var oldVersion = expansionInfo.MobileStatusVersion; Core.Expansion = expansion; - ExpansionInfo.GetInfo(Core.Expansion).MobileStatusVersion = mobileStatusVersion; + ExpansionInfo.CoreExpansion.MobileStatusVersion = mobileStatusVersion; var m = new Mobile((Serial)0x1) { Name = "Random Mobile 1" }; m.DefaultMobileInit(); diff --git a/Projects/Server/Buffers/ValueStringBuilder.cs b/Projects/Server/Buffers/ValueStringBuilder.cs index 6b7746b241..3196ca45e1 100644 --- a/Projects/Server/Buffers/ValueStringBuilder.cs +++ b/Projects/Server/Buffers/ValueStringBuilder.cs @@ -7,7 +7,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Server.Buffers; +using Server.Buffers; + +namespace Server.Text; public ref struct ValueStringBuilder { diff --git a/Projects/Server/Client/ClientVersion.cs b/Projects/Server/Client/ClientVersion.cs index 0ac0e4a070..1116527700 100644 --- a/Projects/Server/Client/ClientVersion.cs +++ b/Projects/Server/Client/ClientVersion.cs @@ -16,7 +16,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using Server.Buffers; +using Server.Text; namespace Server; diff --git a/Projects/Server/Collections/ArrayEnumerator.cs b/Projects/Server/Collections/ArrayEnumerator.cs index ae79d0163f..2783c03f3b 100644 --- a/Projects/Server/Collections/ArrayEnumerator.cs +++ b/Projects/Server/Collections/ArrayEnumerator.cs @@ -48,7 +48,7 @@ public bool MoveNext() if ((uint)_index < (uint)localList.Length) { - _current = _array[_index++]; + _current = localList[_index++]; return true; } diff --git a/Projects/Server/Configuration/ExpansionConfigurationPrompts.cs b/Projects/Server/Configuration/ExpansionConfigurationPrompts.cs new file mode 100644 index 0000000000..54d508b08a --- /dev/null +++ b/Projects/Server/Configuration/ExpansionConfigurationPrompts.cs @@ -0,0 +1,118 @@ +using Server.Maps; +using System; +using System.Globalization; + +namespace Server; + +public static class ExpansionConfigurationPrompts +{ + internal static Expansion GetExpansion() + { + Console.WriteLine("Please choose an expansion by typing the number or short name:"); + var expansions = ExpansionInfo.Table; + + for (var i = 0; i < expansions.Length; i++) + { + var info = expansions[i]; + Console.WriteLine(" - {0,2}: {1} ({2})", i, ((Expansion)info.Id).ToString(), info.Name); + } + + var maxExpansion = (Expansion)expansions[^1].Id; + var maxExpansionName = maxExpansion.ToString(); + + do + { + Console.Write("[enter for {0}]> ", maxExpansionName); + var input = Console.ReadLine(); + Expansion expansion; + + if (string.IsNullOrWhiteSpace(input)) + { + expansion = maxExpansion; + } + else if (int.TryParse(input, NumberStyles.Integer, null, out var number) && + number >= 0 && number < expansions.Length) + { + expansion = (Expansion)number; + } + else if (!Enum.TryParse(input, out expansion)) + { + Utility.PushColor(ConsoleColor.Red); + Console.Write(input); + Utility.PopColor(); + Console.WriteLine(" is an invalid expansion option."); + continue; + } + + Console.Write("Expansion set to "); + Utility.PushColor(ConsoleColor.Green); + Console.Write(ExpansionInfo.GetInfo(expansion).Name); + Utility.PopColor(); + Console.WriteLine("."); + return expansion; + } while (true); + } + + internal static void OutputSelectedMaps(Expansion expansion, MapSelectionFlags selectedMaps) + { + Console.WriteLine("Selected maps:"); + + var i = 0; + foreach (var flag in MapSelection.EnumFromExpansion(expansion)) + { + Console.WriteLine($"{i + 1}. {flag} [{(selectedMaps.Includes(flag) ? "*" : "")}]"); + i++; + } + + Console.WriteLine(); + Console.WriteLine($"[1-{i} and enter to toggle, or enter to finish]"); + } + + internal static MapSelectionFlags GetSelectedMaps(Expansion expansion) + { + var expansionMaps = ExpansionInfo.GetInfo(expansion).MapSelectionFlags; + var selectedMaps = expansionMaps; + + string lastInput; + do + { + OutputSelectedMaps(expansion, selectedMaps); + lastInput = Console.ReadLine()?.TrimEnd(); + + if (string.IsNullOrWhiteSpace(lastInput)) + { + break; + } + + if (!int.TryParse(lastInput, out var selectedNumber)) + { + Console.WriteLine("You need to choose a number, or press ENTER on its own to accept"); + continue; + } + + if (selectedNumber is < 1 or > 31) + { + Console.WriteLine("That number was not an option. Please try again..."); + continue; + } + + var selectedFlag = (MapSelectionFlags)(1 << (selectedNumber - 1)); + + if (!expansionMaps.Includes(selectedFlag)) + { + Console.WriteLine("That number was not an option. Please try again..."); + continue; + } + + selectedMaps.Toggle(selectedFlag); + } while (lastInput != ""); + + Console.WriteLine("These maps will be populated and moongates will not lead to other maps: "); + Utility.PushColor(ConsoleColor.Green); + Console.WriteLine(selectedMaps.ToCommaDelimitedString()); + Utility.PopColor(); + Console.WriteLine($"To change the selected maps, modify {ExpansionInfo.ExpansionConfigurationPath}."); + + return selectedMaps; + } +} diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index 124a6696a3..eb379b7813 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -28,56 +28,57 @@ public static class ServerConfiguration private const string _relPath = "Configuration/modernuo.json"; private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, _relPath); - private static ServerSettings m_Settings; + + private static ServerSettings _settings; private static bool m_Mocked; - public static List AssemblyDirectories => m_Settings.AssemblyDirectories; + public static List AssemblyDirectories => _settings.AssemblyDirectories; - public static HashSet DataDirectories => m_Settings.DataDirectories; + public static HashSet DataDirectories => _settings.DataDirectories; - public static List Listeners => m_Settings.Listeners; + public static List Listeners => _settings.Listeners; public static string ConfigurationFilePath => _relPath; public static ClientVersion GetSetting(string key, ClientVersion defaultValue) => - m_Settings.Settings.TryGetValue(key, out var value) ? new ClientVersion(value) : defaultValue; + _settings.Settings.TryGetValue(key, out var value) ? new ClientVersion(value) : defaultValue; public static string GetSetting(string key, string defaultValue) => - m_Settings.Settings.TryGetValue(key, out var value) ? value : defaultValue; + _settings.Settings.TryGetValue(key, out var value) ? value : defaultValue; public static int GetSetting(string key, int defaultValue) { - m_Settings.Settings.TryGetValue(key, out var strValue); + _settings.Settings.TryGetValue(key, out var strValue); return int.TryParse(strValue, out var value) ? value : defaultValue; } public static long GetSetting(string key, long defaultValue) { - m_Settings.Settings.TryGetValue(key, out var strValue); + _settings.Settings.TryGetValue(key, out var strValue); return long.TryParse(strValue, out var value) ? value : defaultValue; } public static bool GetSetting(string key, bool defaultValue) { - m_Settings.Settings.TryGetValue(key, out var strValue); + _settings.Settings.TryGetValue(key, out var strValue); return bool.TryParse(strValue, out var value) ? value : defaultValue; } public static T GetSetting(string key, T defaultValue) where T : struct, Enum { - m_Settings.Settings.TryGetValue(key, out var strValue); + _settings.Settings.TryGetValue(key, out var strValue); return Enum.TryParse(strValue, out T value) ? value : defaultValue; } public static double GetSetting(string key, double defaultValue) { - m_Settings.Settings.TryGetValue(key, out var strValue); + _settings.Settings.TryGetValue(key, out var strValue); return double.TryParse(strValue, out var value) ? value : defaultValue; } public static T? GetSetting(string key) where T : struct, Enum { - if (!m_Settings.Settings.TryGetValue(key, out var strValue)) + if (!_settings.Settings.TryGetValue(key, out var strValue)) { return null; } @@ -87,7 +88,7 @@ public static double GetSetting(string key, double defaultValue) public static string GetOrUpdateSetting(string key, string defaultValue) { - if (m_Settings.Settings.TryGetValue(key, out var value)) + if (_settings.Settings.TryGetValue(key, out var value)) { return value; } @@ -100,7 +101,7 @@ public static int GetOrUpdateSetting(string key, int defaultValue) { int value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = int.TryParse(strValue, out value) ? value : defaultValue; } @@ -116,7 +117,7 @@ public static long GetOrUpdateSetting(string key, long defaultValue) { long value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = long.TryParse(strValue, out value) ? value : defaultValue; } @@ -132,7 +133,7 @@ public static bool GetOrUpdateSetting(string key, bool defaultValue) { bool value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = bool.TryParse(strValue, out value) ? value : defaultValue; } @@ -148,7 +149,7 @@ public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue) { TimeSpan value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue; } @@ -164,7 +165,7 @@ public static T GetOrUpdateSetting(string key, T defaultValue) where T : stru { T value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = Enum.TryParse(strValue, out value) ? value : defaultValue; } @@ -180,7 +181,7 @@ public static double GetOrUpdateSetting(string key, double defaultValue) { double value; - if (m_Settings.Settings.TryGetValue(key, out var strValue)) + if (_settings.Settings.TryGetValue(key, out var strValue)) { value = double.TryParse(strValue, out value) ? value : defaultValue; } @@ -207,7 +208,7 @@ public static void SetSetting(string key, T value) where T : struct, Enum => public static void SetSetting(string key, string value) { - m_Settings.Settings[key] = value; + _settings.Settings[key] = value; Save(); } @@ -220,9 +221,9 @@ public static void Load(bool mocked = false) if (File.Exists(m_FilePath)) { logger.Information("Reading server configuration from {Path}...", _relPath); - m_Settings = JsonConfig.Deserialize(m_FilePath); + _settings = JsonConfig.Deserialize(m_FilePath); - if (m_Settings == null) + if (_settings == null) { logger.Error("Reading server configuration failed"); throw new FileNotFoundException($"Failed to deserialize {m_FilePath}."); @@ -233,7 +234,7 @@ public static void Load(bool mocked = false) else { updated = true; - m_Settings = new ServerSettings(); + _settings = new ServerSettings(); } if (mocked) @@ -241,12 +242,12 @@ public static void Load(bool mocked = false) return; } - if (m_Settings.DataDirectories.Count == 0) + if (_settings.DataDirectories.Count == 0) { updated = true; foreach (var directory in ServerConfigurationPrompts.GetDataDirectories()) { - m_Settings.DataDirectories.Add(directory); + _settings.DataDirectories.Add(directory); } } @@ -258,19 +259,27 @@ public static void Load(bool mocked = false) DataDirectories.Add(cuoClientFiles); } - if (m_Settings.Listeners.Count == 0) + if (_settings.Listeners.Count == 0) { updated = true; - m_Settings.Listeners.AddRange(ServerConfigurationPrompts.GetListeners()); + _settings.Listeners.AddRange(ServerConfigurationPrompts.GetListeners()); } - if (m_Settings.Expansion == null) + // We have a known, current expansion, so we can deserialize it from Configuration + if (!ExpansionInfo.LoadConfiguration(out var currentExpansion)) { - m_Settings.Expansion = GetSetting("currentExpansion") ?? ServerConfigurationPrompts.GetExpansion(); + currentExpansion = (_settings.Data.Remove("expansion", out var el) + ? el.ToObject(JsonConfig.DefaultOptions) + : null) ?? ExpansionConfigurationPrompts.GetExpansion(); + + // We've updated the selected expansion, so choose the maps we want from it, then store and save our selection + var selectedMaps = ExpansionConfigurationPrompts.GetSelectedMaps(currentExpansion); + ExpansionInfo.StoreMapSelection(selectedMaps, currentExpansion); + updated = true; } - Core.Expansion = m_Settings.Expansion.Value; + Core.Expansion = currentExpansion; if (updated) { @@ -279,6 +288,14 @@ public static void Load(bool mocked = false) Utility.PushColor(ConsoleColor.Green); Console.WriteLine($"{_relPath}."); Utility.PopColor(); + + // Either the expansion.json file has never existed, or it's been deleted, so create it now + ExpansionInfo.SaveConfiguration(); + + Console.Write("Expansion configuration saved to "); + Utility.PushColor(ConsoleColor.Green); + Console.WriteLine($"{ExpansionInfo.ExpansionConfigurationPath}."); + Utility.PopColor(); } } @@ -289,6 +306,6 @@ public static void Save() return; } - JsonConfig.Serialize(m_FilePath, m_Settings); + JsonConfig.Serialize(m_FilePath, _settings); } } diff --git a/Projects/Server/Configuration/ServerConfigurationPrompts.cs b/Projects/Server/Configuration/ServerConfigurationPrompts.cs index 7976870a6a..b1094211ab 100644 --- a/Projects/Server/Configuration/ServerConfigurationPrompts.cs +++ b/Projects/Server/Configuration/ServerConfigurationPrompts.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Net; @@ -83,53 +82,6 @@ internal static bool GetIsClientPre6000() } while (true); } - internal static Expansion GetExpansion() - { - Console.WriteLine("Please choose an expansion by typing the number or short name:"); - var expansions = ExpansionInfo.Table; - - for (int i = 0; i < expansions.Length; i++) - { - var info = expansions[i]; - Console.WriteLine(" - {0,2}: {1} ({2})", i, ((Expansion)info.ID).ToString(), info.Name); - } - - var maxExpansion = (Expansion)expansions[^1].ID; - var maxExpansionName = maxExpansion.ToString(); - - do - { - Console.Write("[enter for {0}]> ", maxExpansionName); - var input = Console.ReadLine(); - Expansion expansion; - - if (string.IsNullOrWhiteSpace(input)) - { - expansion = maxExpansion; - } - else if (int.TryParse(input, NumberStyles.Integer, null, out var number) && - number >= 0 && number < expansions.Length) - { - expansion = (Expansion)number; - } - else if (!Enum.TryParse(input, out expansion)) - { - Utility.PushColor(ConsoleColor.Red); - Console.Write(input); - Utility.PopColor(); - Console.WriteLine(" is an invalid expansion option."); - continue; - } - - Console.Write("Expansion set to "); - Utility.PushColor(ConsoleColor.Green); - Console.Write(ExpansionInfo.GetInfo(expansion).Name); - Utility.PopColor(); - Console.WriteLine("."); - return expansion; - } while (true); - } - internal static List GetDataDirectories() { Console.WriteLine("Please enter the absolute path to your ClassicUO or Ultima Online data:"); @@ -214,7 +166,6 @@ internal static List GetListeners() Utility.PopColor(); Console.WriteLine("."); } while (true); - return ips; } } diff --git a/Projects/Server/Configuration/ServerSettings.cs b/Projects/Server/Configuration/ServerSettings.cs index f95cfff321..21102e0850 100644 --- a/Projects/Server/Configuration/ServerSettings.cs +++ b/Projects/Server/Configuration/ServerSettings.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Net; +using System.Text.Json; using System.Text.Json.Serialization; namespace Server; @@ -30,9 +31,10 @@ public class ServerSettings [JsonPropertyName("listeners")] public List Listeners { get; set; } = new(); - [JsonPropertyName("expansion")] - public Expansion? Expansion { get; set; } - [JsonPropertyName("settings")] public SortedDictionary Settings { get; set; } = new(); + + // For backward compatibility + [JsonExtensionData] + public Dictionary Data { get; set; } = new(); } diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index 519931bd11..9731bf2932 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -14,10 +14,10 @@ *************************************************************************/ using System; -using System.Collections.Generic; using System.IO; using System.Text.Json.Serialization; using Server.Json; +using Server.Maps; namespace Server; @@ -158,7 +158,10 @@ public enum HousingFlags public class ExpansionInfo { + public const string ExpansionConfigurationPath = "Configuration/expansion.json"; + public static bool ForceOldAnimations { get; private set; } + public static void Configure() { ForceOldAnimations = ServerConfiguration.GetSetting("expansion.forceOldAnimations", false); @@ -188,46 +191,44 @@ public static string GetEraFolder(string parentDirectory) return null; } - static ExpansionInfo() + public static void StoreMapSelection(MapSelectionFlags mapSelectionFlags, Expansion expansion) { - var path = Path.Combine(Core.BaseDirectory, "Data/expansion.json"); - if (!File.Exists(path)) + int expansionIndex = (int)expansion; + Table[expansionIndex].MapSelectionFlags = mapSelectionFlags; + } + + public static void SaveConfiguration() + { + var pathToExpansionFile = Path.Combine(Core.BaseDirectory, ExpansionConfigurationPath); + JsonConfig.Serialize(pathToExpansionFile, GetInfo(Core.Expansion)); + } + + public static bool LoadConfiguration(out Expansion expansion) + { + var pathToExpansionFile = Path.Combine(Core.BaseDirectory, ExpansionConfigurationPath); + + ExpansionInfo expansionConfig = JsonConfig.Deserialize(pathToExpansionFile); + if (expansionConfig == null) { - throw new FileNotFoundException($"Expansion file '{path}' could not be found."); + expansion = Expansion.None; + return false; } - var expansions = JsonConfig.Deserialize>(path); - - Table = new ExpansionInfo[expansions.Count]; + int currentExpansionIndex = expansionConfig.Id; + Table[currentExpansionIndex] = expansionConfig; + expansion = (Expansion)currentExpansionIndex; + return true; + } - for (var i = 0; i < expansions.Count; i++) + static ExpansionInfo() + { + var path = Path.Combine(Core.BaseDirectory, "Data/expansions.json"); + if (!File.Exists(path)) { - var expansion = expansions[i]; - if (expansion.ClientVersion != null) - { - Table[i] = new ExpansionInfo( - i, - expansion.Name, - expansion.ClientVersion, - expansion.FeatureFlags, - expansion.CharacterListFlags, - expansion.HousingFlags, - expansion.MobileStatusVersion - ); - } - else - { - Table[i] = new ExpansionInfo( - i, - expansion.Name, - expansion.ClientFlags ?? ClientFlags.None, - expansion.FeatureFlags, - expansion.CharacterListFlags, - expansion.HousingFlags, - expansion.MobileStatusVersion - ); - } + throw new FileNotFoundException($"Expansion file '{path}' could not be found."); } + + Table = JsonConfig.Deserialize(path); } public ExpansionInfo( @@ -237,8 +238,9 @@ public ExpansionInfo( FeatureFlags supportedFeatures, CharacterListFlags charListFlags, HousingFlags customHousingFlag, - int mobileStatusVersion - ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion) => + int mobileStatusVersion, + MapSelectionFlags mapSelectionFlags + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion, mapSelectionFlags) => ClientFlags = clientFlags; public ExpansionInfo( @@ -248,41 +250,54 @@ public ExpansionInfo( FeatureFlags supportedFeatures, CharacterListFlags charListFlags, HousingFlags customHousingFlag, - int mobileStatusVersion - ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion) => + int mobileStatusVersion, + MapSelectionFlags mapSelectionFlags + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion, mapSelectionFlags) => RequiredClient = requiredClient; - private ExpansionInfo( + [JsonConstructor] + public ExpansionInfo( int id, string name, FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag, - int mobileStatusVersion + CharacterListFlags characterListFlags, + HousingFlags housingFlags, + int mobileStatusVersion, + MapSelectionFlags mapSelectionFlags ) { - ID = id; + Id = id; Name = name; SupportedFeatures = supportedFeatures; - CharacterListFlags = charListFlags; - CustomHousingFlag = customHousingFlag; + CharacterListFlags = characterListFlags; + HousingFlags = housingFlags; MobileStatusVersion = mobileStatusVersion; + MapSelectionFlags = mapSelectionFlags; } public static ExpansionInfo CoreExpansion => GetInfo(Core.Expansion); public static ExpansionInfo[] Table { get; } - public int ID { get; } + public int Id { get; } public string Name { get; set; } public ClientFlags ClientFlags { get; set; } + + [JsonConverter(typeof(FlagsConverter))] public FeatureFlags SupportedFeatures { get; set; } + + [JsonConverter(typeof(FlagsConverter))] public CharacterListFlags CharacterListFlags { get; set; } public ClientVersion RequiredClient { get; set; } - public HousingFlags CustomHousingFlag { get; set; } + + [JsonConverter(typeof(FlagsConverter))] + public HousingFlags HousingFlags { get; set; } public int MobileStatusVersion { get; set; } + [JsonConverter(typeof(FlagsConverter))] + public MapSelectionFlags MapSelectionFlags { get; set; } + public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex); public static ExpansionInfo GetInfo(int ex) @@ -299,23 +314,3 @@ public static ExpansionInfo GetInfo(int ex) public override string ToString() => Name; } - -public record ExpansionConfig -{ - public string Name { get; init; } - - public ClientVersion? ClientVersion { get; init; } - - public ClientFlags? ClientFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public FeatureFlags FeatureFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public CharacterListFlags CharacterListFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public HousingFlags HousingFlags { get; init; } - - public int MobileStatusVersion { get; set; } -} diff --git a/Projects/Server/Json/Converters/TextDefinitionConverter.cs b/Projects/Server/Json/Converters/TextDefinitionConverter.cs index 1b5f1b1fb1..dd1a1cd13d 100644 --- a/Projects/Server/Json/Converters/TextDefinitionConverter.cs +++ b/Projects/Server/Json/Converters/TextDefinitionConverter.cs @@ -17,28 +17,27 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Server.Json +namespace Server.Json; + +public class TextDefinitionConverter : JsonConverter { - public class TextDefinitionConverter : JsonConverter - { - public override TextDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - reader.TokenType switch - { - JsonTokenType.String => TextDefinition.Of(reader.GetString()), - JsonTokenType.Number => TextDefinition.Of(reader.GetInt32()), - _ => throw new JsonException("TextDefinition value must be an integer or string") - }; + public override TextDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.TokenType switch + { + JsonTokenType.String => TextDefinition.Of(reader.GetString()), + JsonTokenType.Number => TextDefinition.Of(reader.GetInt32()), + _ => throw new JsonException("TextDefinition value must be an integer or string") + }; - public override void Write(Utf8JsonWriter writer, TextDefinition value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TextDefinition value, JsonSerializerOptions options) + { + if (value.Number > 0) + { + writer.WriteNumberValue(value.Number); + } + else { - if (value.Number > 0) - { - writer.WriteNumberValue(value.Number); - } - else - { - writer.WriteStringValue(value.String); - } + writer.WriteStringValue(value.String); } } -} +} \ No newline at end of file diff --git a/Projects/Server/Json/Converters/TextDefinitionConverterFactory.cs b/Projects/Server/Json/Converters/TextDefinitionConverterFactory.cs index 8bd19578c6..bcedb9606c 100644 --- a/Projects/Server/Json/Converters/TextDefinitionConverterFactory.cs +++ b/Projects/Server/Json/Converters/TextDefinitionConverterFactory.cs @@ -17,13 +17,12 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Server.Json +namespace Server.Json; + +public class TextDefinitionConverterFactory : JsonConverterFactory { - public class TextDefinitionConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextDefinition); + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextDefinition); - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => - new TextDefinitionConverter(); - } -} + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new TextDefinitionConverter(); +} \ No newline at end of file diff --git a/Projects/Server/Localization/LocalizationEntry.cs b/Projects/Server/Localization/LocalizationEntry.cs index d611222dce..19d90efa40 100644 --- a/Projects/Server/Localization/LocalizationEntry.cs +++ b/Projects/Server/Localization/LocalizationEntry.cs @@ -17,6 +17,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Server.Buffers; +using Server.Text; using Server.Collections; namespace Server; diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index cd60c0f6d1..736938d673 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -25,10 +25,10 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Server.Buffers; using Server.Json; using Server.Logging; using Server.Network; +using Server.Text; namespace Server; @@ -227,7 +227,6 @@ public static string Arguments public static int ScriptMobiles => _mobileCount; public static Expansion Expansion { get; set; } - public static bool T2A => Expansion >= Expansion.T2A; public static bool UOR => Expansion >= Expansion.UOR; diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs index 10d0edf19c..19491cf919 100644 --- a/Projects/Server/Maps/Map.cs +++ b/Projects/Server/Maps/Map.cs @@ -320,7 +320,6 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public const int SectorActiveRange = 2; private static ILogger logger = LogFactory.GetLogger(typeof(Map)); - private readonly int m_FileIndex; private readonly Sector[][] m_Sectors; private readonly int m_SectorsHeight; diff --git a/Projects/Server/Maps/MapSelection.cs b/Projects/Server/Maps/MapSelection.cs new file mode 100644 index 0000000000..a0988c0a68 --- /dev/null +++ b/Projects/Server/Maps/MapSelection.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.CompilerServices; +using Server.Text; + +namespace Server.Maps; + +[Flags] +public enum MapSelectionFlags +{ + Felucca = 0x00000001, + Trammel = 0x00000002, + Ilshenar = 0x00000004, + Malas = 0x00000008, + Tokuno = 0x00000010, + TerMur = 0x00000020 +} + +public static class MapSelection +{ + public static MapSelectionFlags[] MapSelectionValues { get; } = Enum.GetValues(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Includes(this MapSelectionFlags flags, MapSelectionFlags flag) => (flags & flag) == flag; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Toggle(this ref MapSelectionFlags flags, MapSelectionFlags flag) + { + flags ^= flag; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Enable(this ref MapSelectionFlags flags, MapSelectionFlags flag) + { + flags |= flag; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Disable(this ref MapSelectionFlags flags, MapSelectionFlags flag) + { + flags &= ~flag; + } + + public static string ToCommaDelimitedString(this MapSelectionFlags flags) + { + using var builder = ValueStringBuilder.Create(); + + foreach (var flag in flags.GetEnumerable()) + { + builder.Append(builder.Length > 0 ? $", {flag}" : $"{flag}"); + } + + return builder.Length == 0 ? "None" : builder.ToString(); + } + + public static MapSelectionFlags ToSelectionFlag(this Map map) => (MapSelectionFlags)(1 << map.MapID); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MapSelectionEnumerable GetEnumerable(this MapSelectionFlags flags) => new(flags); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MapSelectionEnumerable EnumFromExpansion(Expansion expansion) => ExpansionInfo.GetInfo(expansion).MapSelectionFlags.GetEnumerable(); + + public class MapSelectionEnumerable + { + private readonly MapSelectionFlags _flags; + + public MapSelectionEnumerable(MapSelectionFlags flags) => _flags = flags; + + public MapSelectionEnumerator GetEnumerator() => new(MapSelectionValues, _flags); + } + + public ref struct MapSelectionEnumerator + { + private readonly MapSelectionFlags[] _allMaps; + private readonly MapSelectionFlags _mapSelections; + private int _index; + private MapSelectionFlags _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal MapSelectionEnumerator(MapSelectionFlags[] allMaps, MapSelectionFlags mapSelections) + { + _allMaps = allMaps; + _mapSelections = mapSelections; + _index = 0; + _current = default; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + MapSelectionFlags[] localList = _allMaps; + + while ((uint)_index < (uint)localList.Length) + { + _current = localList[_index++]; + + if ((_mapSelections & _current) == _current) + { + return true; + } + } + + return false; + } + + public MapSelectionFlags Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current; + } + } +} + diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index f7ae20bbe1..ff51af026f 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Server.Accounting; -using Server.Buffers; using Server.ContextMenus; using Server.Guilds; using Server.Gumps; @@ -14,6 +13,7 @@ using Server.Network; using Server.Prompts; using Server.Targeting; +using Server.Text; using Server.Utilities; using CalcMoves = Server.Movement.Movement; diff --git a/Projects/Server/Network/NetState/NetState.ClientVersion.cs b/Projects/Server/Network/NetState/NetState.ClientVersion.cs index fdbc0b4ccc..5d84733ac8 100644 --- a/Projects/Server/Network/NetState/NetState.ClientVersion.cs +++ b/Projects/Server/Network/NetState/NetState.ClientVersion.cs @@ -104,7 +104,7 @@ public ExpansionInfo ExpansionInfo } public bool SupportsExpansion(ExpansionInfo info, bool checkCoreExpansion = true) => - info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.ID) && ExpansionInfo.ID >= info.ID; + info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.Id) && ExpansionInfo.Id >= info.Id; public bool SupportsExpansion(Expansion ex, bool checkCoreExpansion = true) => SupportsExpansion(ExpansionInfo.GetInfo(ex), checkCoreExpansion); diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index e32ea08238..ecbce2bbca 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -463,7 +463,7 @@ public static void SendMobileStatus(this NetState ns, Mobile beholder, Mobile be else { var maxVersion = ExpansionInfo.CoreExpansion.MobileStatusVersion; - var nsExpansion = (Expansion)ns.ExpansionInfo.ID; + var nsExpansion = (Expansion)ns.ExpansionInfo.Id; if (maxVersion >= 6 && nsExpansion >= Expansion.HS && ns.ExtendedStatus) { diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index 15524dddcc..85a1cb06c6 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Server.Json; using Server.Logging; diff --git a/Projects/Server/Skills.cs b/Projects/Server/Skills.cs index 60f5342643..b4ffaf83df 100644 --- a/Projects/Server/Skills.cs +++ b/Projects/Server/Skills.cs @@ -860,7 +860,7 @@ public bool MoveNext() while ((uint)_index < (uint)localList.Length) { - _current = _skills[_index++]; + _current = localList[_index++]; if (_current != null) { return true; diff --git a/Projects/Server/Text/TextEncoding.cs b/Projects/Server/Text/TextEncoding.cs index d50ca3f7e0..d9e7c9e56b 100644 --- a/Projects/Server/Text/TextEncoding.cs +++ b/Projects/Server/Text/TextEncoding.cs @@ -16,7 +16,6 @@ using System; using System.Runtime.CompilerServices; using System.Text; -using Server.Buffers; namespace Server.Text; diff --git a/Projects/UOContent.Tests/Fixtures/ServerFixture.cs b/Projects/UOContent.Tests/Fixtures/ServerFixture.cs index 3a9d0c3d35..086fef285a 100644 --- a/Projects/UOContent.Tests/Fixtures/ServerFixture.cs +++ b/Projects/UOContent.Tests/Fixtures/ServerFixture.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Reflection; using Server.Misc; diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 88b6643f42..a077706da5 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -1192,7 +1192,7 @@ public bool MoveNext() while ((uint)_index < (uint)localList.Length) { - _current = _mobiles[_index++]; + _current = localList[_index++]; if (_current?.Deleted == false) { return true; diff --git a/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs index 55f16f754c..eb4a1b9686 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/BaseCommandImplementor.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Server.Buffers; +using Server.Text; namespace Server.Commands.Generic { diff --git a/Projects/UOContent/Commands/Properties.cs b/Projects/UOContent/Commands/Properties.cs index 5bd2f818f0..197b9702b2 100644 --- a/Projects/UOContent/Commands/Properties.cs +++ b/Projects/UOContent/Commands/Properties.cs @@ -1,10 +1,10 @@ using System; using System.Reflection; -using Server.Buffers; using Server.Commands; using Server.Commands.Generic; using Server.Gumps; using Server.Targeting; +using Server.Text; using CPA = Server.CommandPropertyAttribute; using static Server.Attributes; diff --git a/Projects/UOContent/Compression/TarArchive.cs b/Projects/UOContent/Compression/TarArchive.cs index babb5e1bc4..4c5f8cd1a8 100755 --- a/Projects/UOContent/Compression/TarArchive.cs +++ b/Projects/UOContent/Compression/TarArchive.cs @@ -4,7 +4,7 @@ using System.IO; using System.IO.Compression; using System.Net.Http; -using Server.Buffers; +using Server.Text; namespace Server.Compression { diff --git a/Projects/UOContent/Engines/Ethics/Evil/Powers/UnholySense.cs b/Projects/UOContent/Engines/Ethics/Evil/Powers/UnholySense.cs index 5629a45b58..ac884d9507 100644 --- a/Projects/UOContent/Engines/Ethics/Evil/Powers/UnholySense.cs +++ b/Projects/UOContent/Engines/Ethics/Evil/Powers/UnholySense.cs @@ -1,5 +1,5 @@ using System; -using Server.Buffers; +using Server.Text; namespace Server.Ethics.Evil { diff --git a/Projects/UOContent/Engines/Ethics/Hero/Powers/HolySense.cs b/Projects/UOContent/Engines/Ethics/Hero/Powers/HolySense.cs index ed4240af85..ee16f60cac 100644 --- a/Projects/UOContent/Engines/Ethics/Hero/Powers/HolySense.cs +++ b/Projects/UOContent/Engines/Ethics/Hero/Powers/HolySense.cs @@ -1,5 +1,5 @@ using System; -using Server.Buffers; +using Server.Text; namespace Server.Ethics.Hero { diff --git a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs index a8160ec067..e28bc82048 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs @@ -14,11 +14,11 @@ *************************************************************************/ using System.Collections.Generic; -using Server.Buffers; using Server.Commands.Generic; using Server.Engines.Spawners; using Server.Mobiles; using Server.Network; +using Server.Text; using CPA = Server.CommandPropertyAttribute; namespace Server.Gumps diff --git a/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs b/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs index c98b9c4f51..003aad4059 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnerEntry.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using Server.Mobiles; namespace Server.Engines.Spawners { diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs index 10cc79e73d..03e4212153 100644 --- a/Projects/UOContent/Gumps/AdminGump.cs +++ b/Projects/UOContent/Gumps/AdminGump.cs @@ -5,13 +5,14 @@ using System.Runtime.InteropServices; using System.Threading; using Server.Accounting; -using Server.Buffers; using Server.Collections; using Server.Commands; +using Server.Maps; using Server.Misc; using Server.Multis; using Server.Network; using Server.Prompts; +using Server.Text; namespace Server.Gumps { @@ -2085,29 +2086,37 @@ public override void OnResponse(NetState sender, RelayInfo info) } case 103: { - if (Core.SA) + var folder = Core.SA ? "post-uoml" : "uoml"; + + var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags; + if (Core.SA && availableMaps.Includes(MapSelectionFlags.TerMur)) { - InvokeCommand("GenerateSpawners Data/Spawns/post-uoml/*/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/post-uoml/termur/*.json"); } - else + + if (availableMaps.Includes(MapSelectionFlags.Malas)) { - if (Core.ML) - { - InvokeCommand("GenerateSpawners Data/Spawns/uoml/malas/*.json"); - } + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/malas/*.json"); + } - if (Core.SE) - { - InvokeCommand("GenerateSpawners Data/Spawns/uoml/tokuno/*.json"); - } + if (availableMaps.Includes(MapSelectionFlags.Tokuno)) + { + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/tokuno/*.json"); + } - if (Core.AOS) - { - InvokeCommand("GenerateSpawners Data/Spawns/uoml/ilshenar/*.json"); - } + if (availableMaps.Includes(MapSelectionFlags.Ilshenar)) + { + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/ilshenar/*.json"); + } - InvokeCommand("GenerateSpawners Data/Spawns/uoml/trammel/*.json"); - InvokeCommand("GenerateSpawners Data/Spawns/uoml/felucca/*.json"); + if (availableMaps.Includes(MapSelectionFlags.Trammel)) + { + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/trammel/*.json"); + } + + if (availableMaps.Includes(MapSelectionFlags.Felucca)) + { + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/felucca/*.json"); } notice = "Spawners have been generated."; diff --git a/Projects/UOContent/Items/Books/BaseBook.cs b/Projects/UOContent/Items/Books/BaseBook.cs index 006f138868..dbf5f2dc38 100644 --- a/Projects/UOContent/Items/Books/BaseBook.cs +++ b/Projects/UOContent/Items/Books/BaseBook.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; -using Server.Buffers; using Server.ContextMenus; using Server.Gumps; using Server.Multis; +using Server.Text; namespace Server.Items { diff --git a/Projects/UOContent/Items/Misc/PublicMoongate.cs b/Projects/UOContent/Items/Misc/PublicMoongate.cs index 4ddffbfa42..459d40d221 100644 --- a/Projects/UOContent/Items/Misc/PublicMoongate.cs +++ b/Projects/UOContent/Items/Misc/PublicMoongate.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; using Server.Factions; using Server.Gumps; +using Server.Maps; using Server.Mobiles; using Server.Network; using Server.Spells; @@ -105,11 +107,11 @@ public static void MoonGen_OnCommand(CommandEventArgs e) var count = 0; - count += MoonGen(PMList.Trammel); - count += MoonGen(PMList.Felucca); - count += MoonGen(PMList.Ilshenar); - count += MoonGen(PMList.Malas); - count += MoonGen(PMList.Tokuno); + count += MoonGen(PMList.Trammel, MapSelectionFlags.Trammel); + count += MoonGen(PMList.Felucca, MapSelectionFlags.Felucca); + count += MoonGen(PMList.Ilshenar, MapSelectionFlags.Ilshenar); + count += MoonGen(PMList.Malas, MapSelectionFlags.Malas); + count += MoonGen(PMList.Tokuno, MapSelectionFlags.Tokuno); World.Broadcast(0x35, true, $"{count} moongates generated."); } @@ -137,8 +139,13 @@ private static void DeleteAll() } } - private static int MoonGen(PMList list) + private static int MoonGen(PMList list, MapSelectionFlags flag) { + if (!ExpansionInfo.CoreExpansion.MapSelectionFlags.Includes(flag)) + { + return 0; + } + foreach (var entry in list.Entries) { Item item = new PublicMoongate(); @@ -355,11 +362,23 @@ public MoongateGump(Mobile mobile, Item moongate) : base(100, 100) }; } - _lists = new PMList[checkLists.Length]; + var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags; + using var filteredBySelectedMaps = PooledRefList.Create(); - for (var i = 0; i < _lists.Length; ++i) + for (var i = 0; i < checkLists.Length; ++i) { - _lists[i] = checkLists[i]; + var pmList = checkLists[i]; + if (availableMaps.Includes(pmList.Map.ToSelectionFlag())) + { + filteredBySelectedMaps.Add(pmList); + } + } + + int mapCount = filteredBySelectedMaps.Count; + _lists = new PMList[mapCount]; + for (var i = 0; i < mapCount; i++) + { + _lists[i] = filteredBySelectedMaps[i]; } for (var i = 0; i < _lists.Length; ++i) @@ -383,15 +402,15 @@ public MoongateGump(Mobile mobile, Item moongate) : base(100, 100) AddHtmlLocalized(5, 5, 200, 20, 1012011); // Pick your destination: - for (var i = 0; i < checkLists.Length; ++i) + for (var i = 0; i < filteredBySelectedMaps.Count; ++i) { - AddButton(10, 35 + i * 25, 2117, 2118, 0, GumpButtonType.Page, Array.IndexOf(_lists, checkLists[i]) + 1); - AddHtmlLocalized(30, 35 + i * 25, 150, 20, checkLists[i].Number); + AddButton(10, 35 + i * 25, 2117, 2118, 0, GumpButtonType.Page, Array.IndexOf(_lists, filteredBySelectedMaps[i]) + 1); + AddHtmlLocalized(30, 35 + i * 25, 150, 20, filteredBySelectedMaps[i].Number); } for (var i = 0; i < _lists.Length; ++i) { - RenderPage(i, Array.IndexOf(checkLists, _lists[i])); + RenderPage(i, filteredBySelectedMaps.IndexOf(_lists[i])); } } diff --git a/Projects/UOContent/Items/Misc/Teleporter.cs b/Projects/UOContent/Items/Misc/Teleporter.cs index af981b4a1b..cecf3e5440 100644 --- a/Projects/UOContent/Items/Misc/Teleporter.cs +++ b/Projects/UOContent/Items/Misc/Teleporter.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; -using Server.Buffers; using Server.Mobiles; using Server.Network; using Server.Spells; +using Server.Text; namespace Server.Items; diff --git a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs index 30a2c41b0f..a0d7552ca9 100644 --- a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs +++ b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs @@ -3,12 +3,12 @@ using System.Net; using ModernUO.Serialization; using Server.Accounting; -using Server.Buffers; using Server.ContextMenus; using Server.Gumps; using Server.Mobiles; using Server.Network; using Server.Regions; +using Server.Text; namespace Server.Items; diff --git a/Projects/UOContent/Misc/CharacterCreation.cs b/Projects/UOContent/Misc/CharacterCreation.cs index 9f4772ff4b..1439a5fbcf 100644 --- a/Projects/UOContent/Misc/CharacterCreation.cs +++ b/Projects/UOContent/Misc/CharacterCreation.cs @@ -4,6 +4,7 @@ using Server.Accounting; using Server.Items; using Server.Logging; +using Server.Maps; using Server.Mobiles; using Server.Network; @@ -197,7 +198,6 @@ private static void EventSink_CharacterCreated(CharacterCreatedEventArgs args) } var city = GetStartLocation(args, young); - newChar.MoveToWorld(city.Location, city.Map); logger.Information( @@ -218,11 +218,13 @@ public static bool VerifyProfession(int profession) => private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool isYoung) { + // We don't get the actual client version until after character creation var post6000Supported = !TileMatrix.Pre6000ClientSupport; + var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags; - if (Core.ML && post6000Supported) + if (Core.ML && post6000Supported && availableMaps.Includes(MapSelectionFlags.Trammel)) { - return _newHavenInfo; // We don't get the client Version until AFTER Character creation + return _newHavenInfo; } var useHaven = isYoung; @@ -236,7 +238,7 @@ private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool is { case "necromancer": { - if ((flags & ClientFlags.Malas) != 0) + if ((flags & ClientFlags.Malas) != 0 && availableMaps.Includes(MapSelectionFlags.Malas)) { return new CityInfo("Umbra", "Mardoth's Tower", 2114, 1301, -50, Map.Malas); } @@ -256,11 +258,21 @@ private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool is } case "paladin": { - return _newHavenInfo; + if (availableMaps.Includes(MapSelectionFlags.Trammel) && post6000Supported) + { + return _newHavenInfo; + } + + break; } case "samurai": { - if ((flags & ClientFlags.Tokuno) != 0) + bool haotisAndTokunoAccessible = + (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && + (flags & ClientFlags.Malas) == ClientFlags.Malas && + availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); + + if (haotisAndTokunoAccessible) { return new CityInfo("Samurai DE", "Haoti's Grounds", 368, 780, -1, Map.Malas); } @@ -280,7 +292,12 @@ private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool is } case "ninja": { - if ((flags & ClientFlags.Tokuno) != 0) + bool enimosAndTokunoAccessible = + (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && + (flags & ClientFlags.Malas) == ClientFlags.Malas && + availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); + + if (enimosAndTokunoAccessible) { return new CityInfo("Ninja DE", "Enimo's Residence", 414, 823, -1, Map.Malas); } @@ -300,7 +317,43 @@ private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool is } } - return post6000Supported && useHaven ? _newHavenInfo : args.City; + if (post6000Supported && useHaven && availableMaps.Includes(MapSelectionFlags.Trammel)) + { + // New Haven is supported, so put them there... + // Note: if your server maps don't contain New Haven, this will place + // them in the wilderness of Ocllo + return _newHavenInfo; + } + + if (useHaven) + { + // New Haven is not available, so place them in Ocllo instead, if they're aiming for Haven + CityInfo oclloBank = new CityInfo("Ocllo", "Near the bank", 3677, 2513, -1, Map.Trammel); + if (availableMaps.Includes(MapSelectionFlags.Trammel)) + { + return oclloBank; + } + + if (availableMaps.Includes(MapSelectionFlags.Felucca)) + { + oclloBank.Map = Map.Felucca; + return oclloBank; + } + } + + // They're not trying to get to Haven, so use their city selection + // instead - adjusted according to available maps + if (args.City.Map == Map.Trammel && !availableMaps.Includes(MapSelectionFlags.Trammel)) + { + args.City.Map = Map.Felucca; + } + + if (args.City.Map == Map.Felucca && !availableMaps.Includes(MapSelectionFlags.Felucca)) + { + args.City.Map = Map.Trammel; + } + + return args.City; } private static void SetStats(Mobile m, NetState state, StatNameValue[] stats, int prof) diff --git a/Projects/UOContent/Misc/ClientVerification.cs b/Projects/UOContent/Misc/ClientVerification.cs index 80b8d9a819..8a1e306107 100644 --- a/Projects/UOContent/Misc/ClientVerification.cs +++ b/Projects/UOContent/Misc/ClientVerification.cs @@ -1,9 +1,9 @@ using System; -using Server.Buffers; using Server.Gumps; using Server.Logging; using Server.Mobiles; using Server.Network; +using Server.Text; namespace Server.Misc { diff --git a/Projects/UOContent/Misc/InhumanSpeech.cs b/Projects/UOContent/Misc/InhumanSpeech.cs index 1791082e19..41f0e51b70 100644 --- a/Projects/UOContent/Misc/InhumanSpeech.cs +++ b/Projects/UOContent/Misc/InhumanSpeech.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Server.Buffers; +using Server.Text; namespace Server.Misc { diff --git a/Projects/UOContent/Misc/Titles.cs b/Projects/UOContent/Misc/Titles.cs index 7b293356e4..8c002515b6 100644 --- a/Projects/UOContent/Misc/Titles.cs +++ b/Projects/UOContent/Misc/Titles.cs @@ -1,8 +1,7 @@ using System; -using System.Text; -using Server.Buffers; using Server.Engines.CannedEvil; using Server.Mobiles; +using Server.Text; namespace Server.Misc { diff --git a/Projects/UOContent/Multis/ComponentVerification.cs b/Projects/UOContent/Multis/ComponentVerification.cs index 7d4bd9c91c..f0fd40d089 100644 --- a/Projects/UOContent/Multis/ComponentVerification.cs +++ b/Projects/UOContent/Multis/ComponentVerification.cs @@ -133,7 +133,7 @@ public bool IsMultiValid(int multiID) => multiID > 0 && multiID < m_MultiTable.Length && CheckValidity(m_MultiTable[multiID]); public bool CheckValidity(int val) => - val != -1 && (val == 0 || ((int)ExpansionInfo.CoreExpansion.CustomHousingFlag & val) != 0); + val != -1 && (val == 0 || ((int)ExpansionInfo.CoreExpansion.HousingFlags & val) != 0); private int[] CreateTable(int length) { diff --git a/Projects/UOContent/Skills/ForensicEval.cs b/Projects/UOContent/Skills/ForensicEval.cs index de462162e9..b2fd41939b 100644 --- a/Projects/UOContent/Skills/ForensicEval.cs +++ b/Projects/UOContent/Skills/ForensicEval.cs @@ -1,8 +1,8 @@ using System; -using Server.Buffers; using Server.Items; using Server.Mobiles; using Server.Targeting; +using Server.Text; namespace Server.SkillHandlers {