diff --git a/Distribution/Data/expansion.json b/Distribution/Data/expansion.json
new file mode 100644
index 000000000..e0b036f61
--- /dev/null
+++ b/Distribution/Data/expansion.json
@@ -0,0 +1,794 @@
+[
+ {
+ "Name": "None",
+ "ClientVersion": null,
+ "ClientFlags": null,
+ "FeatureFlags": {
+ "None": true,
+ "T2A": false,
+ "UOR": false,
+ "UOTD": false,
+ "LBR": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": true,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": false,
+ "SlotLimit": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": true,
+ "AOS": false,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "The Second Age",
+ "ClientVersion": null,
+ "ClientFlags": "Felucca",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": false,
+ "UOTD": false,
+ "LBR": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": true,
+ "AOS": false,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Renaissance",
+ "ClientVersion": null,
+ "ClientFlags": "Trammel",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": false,
+ "LBR": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": true,
+ "AOS": false,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Third Dawn",
+ "ClientVersion": null,
+ "ClientFlags": "Ilshenar",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": true,
+ "AOS": false,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Blackthorn's Revenge",
+ "ClientVersion": null,
+ "ClientFlags": "Ilshenar",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": false,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": true,
+ "AOS": false,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Age of Shadows",
+ "ClientVersion": null,
+ "ClientFlags": "Malas",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": false,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": false,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Samurai Empire",
+ "ClientVersion": null,
+ "ClientFlags": "Tokuno",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": false,
+ "EigthAge": false,
+ "NinthAge": false,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": false,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": false,
+ "Crystal": false,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Mondain's Legacy",
+ "ClientVersion": "5.0.0a",
+ "ClientFlags": null,
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "EigthAge": false,
+ "NinthAge": true,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": true,
+ "Crystal": true,
+ "SA": false,
+ "HS": false,
+ "Gothic": false,
+ "Rustic": false,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Stygian Abyss",
+ "ClientVersion": null,
+ "ClientFlags": "TerMur",
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "EigthAge": false,
+ "NinthAge": true,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": true,
+ "HS": false,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": true,
+ "Crystal": true,
+ "SA": true,
+ "HS": false,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "High Seas",
+ "ClientVersion": "7.0.9.0",
+ "ClientFlags": null,
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "EigthAge": false,
+ "NinthAge": true,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": true,
+ "Crystal": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": false,
+ "Shadowguard": false,
+ "TOL": false,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Time of Legends",
+ "ClientVersion": "7.0.45.65",
+ "ClientFlags": null,
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "EigthAge": false,
+ "NinthAge": true,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": true,
+ "Shadowguard": true,
+ "TOL": true,
+ "EJ": false
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": true,
+ "Crystal": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": true,
+ "Shadowguard": true,
+ "TOL": true,
+ "EJ": false
+ }
+ },
+ {
+ "Name": "Endless Journey",
+ "ClientVersion": "7.0.61.0",
+ "ClientFlags": null,
+ "FeatureFlags": {
+ "None": false,
+ "T2A": true,
+ "UOR": true,
+ "UOTD": true,
+ "LBR": true,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "EigthAge": false,
+ "NinthAge": true,
+ "TenthAge": false,
+ "IncreasedStorage": false,
+ "SeventhCharacterSlot": false,
+ "RoleplayFaces": false,
+ "TrialAccount": false,
+ "LiveAccount": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": true,
+ "Shadowguard": true,
+ "TOL": true,
+ "EJ": true
+ },
+ "CharacterListFlags": {
+ "None": false,
+ "Unk1": false,
+ "OverwriteConfigButton": false,
+ "OneCharacterSlot": false,
+ "ContextMenus": true,
+ "SlotLimit": false,
+ "AOS": true,
+ "SixthCharacterSlot": false,
+ "SE": true,
+ "ML": true,
+ "Unk2": false,
+ "UO3DClientType": false,
+ "Unk3": false,
+ "SeventhCharacterSlot": false,
+ "Unk4": false,
+ "NewMovementSystem": false,
+ "NewFeluccaAreas": false
+ },
+ "HousingFlags": {
+ "None": false,
+ "AOS": true,
+ "SE": true,
+ "ML": true,
+ "Crystal": true,
+ "SA": true,
+ "HS": true,
+ "Gothic": true,
+ "Rustic": true,
+ "Jungle": true,
+ "Shadowguard": true,
+ "TOL": true,
+ "EJ": true
+ }
+ }
+]
diff --git a/Projects/Server.Tests/Fixtures/ServerFixture.cs b/Projects/Server.Tests/Fixtures/ServerFixture.cs
index 4b0b7cae6..231f3ad25 100644
--- a/Projects/Server.Tests/Fixtures/ServerFixture.cs
+++ b/Projects/Server.Tests/Fixtures/ServerFixture.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reflection;
namespace Server.Tests
{
@@ -7,8 +8,8 @@ namespace Server.Tests
// Global setup
static ServerFixture()
{
+ Core.Assembly = Assembly.GetExecutingAssembly();
Core.LoopContext = new EventLoopContext();
-
Core.Expansion = Expansion.EJ;
// Load Configurations
diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj
index 2e428c64e..12e6fd669 100644
--- a/Projects/Server.Tests/Server.Tests.csproj
+++ b/Projects/Server.Tests/Server.Tests.csproj
@@ -10,5 +10,9 @@
+
+
+
+
diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs
index 4ed98bea1..7f38b22c7 100644
--- a/Projects/Server/ExpansionInfo.cs
+++ b/Projects/Server/ExpansionInfo.cs
@@ -1,4 +1,23 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: ExpansionInfo.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json.Serialization;
+using Server.Json;
namespace Server
{
@@ -141,105 +160,37 @@ namespace Server
{
static ExpansionInfo()
{
- Table = new[]
+ var path = Path.Combine(Core.BaseDirectory, "Data/expansion.json");
+ var expansions = JsonConfig.Deserialize>(path);
+
+ Table = new ExpansionInfo[expansions.Count];
+
+ for (var i = 0; i < expansions.Count; i++)
{
- new ExpansionInfo(
- 0,
- "None",
- ClientFlags.None,
- FeatureFlags.ExpansionNone,
- CharacterListFlags.ExpansionNone,
- HousingFlags.None
- ),
- new ExpansionInfo(
- 1,
- "The Second Age",
- ClientFlags.Felucca,
- FeatureFlags.ExpansionT2A,
- CharacterListFlags.ExpansionT2A,
- HousingFlags.None
- ),
- new ExpansionInfo(
- 2,
- "Renaissance",
- ClientFlags.Trammel,
- FeatureFlags.ExpansionUOR,
- CharacterListFlags.ExpansionUOR,
- HousingFlags.None
- ),
- new ExpansionInfo(
- 3,
- "Third Dawn",
- ClientFlags.Ilshenar,
- FeatureFlags.ExpansionUOTD,
- CharacterListFlags.ExpansionUOTD,
- HousingFlags.None
- ),
- new ExpansionInfo(
- 4,
- "Blackthorn's Revenge",
- ClientFlags.Ilshenar,
- FeatureFlags.ExpansionLBR,
- CharacterListFlags.ExpansionLBR,
- HousingFlags.None
- ),
- new ExpansionInfo(
- 5,
- "Age of Shadows",
- ClientFlags.Malas,
- FeatureFlags.ExpansionAOS,
- CharacterListFlags.ExpansionAOS,
- HousingFlags.HousingAOS
- ),
- new ExpansionInfo(
- 6,
- "Samurai Empire",
- ClientFlags.Tokuno,
- FeatureFlags.ExpansionSE,
- CharacterListFlags.ExpansionSE,
- HousingFlags.HousingSE
- ),
- new ExpansionInfo(
- 7,
- "Mondain's Legacy",
- new ClientVersion("5.0.0a"),
- FeatureFlags.ExpansionML,
- CharacterListFlags.ExpansionML,
- HousingFlags.HousingML
- ),
- new ExpansionInfo(
- 8,
- "Stygian Abyss",
- ClientFlags.TerMur,
- FeatureFlags.ExpansionSA,
- CharacterListFlags.ExpansionSA,
- HousingFlags.HousingSA
- ),
- new ExpansionInfo(
- 9,
- "High Seas",
- new ClientVersion("7.0.9.0"),
- FeatureFlags.ExpansionHS,
- CharacterListFlags.ExpansionHS,
- HousingFlags.HousingHS
- ),
- new ExpansionInfo(
- 10,
- "Time of Legends",
- new ClientVersion("7.0.45.65"),
- FeatureFlags.ExpansionTOL,
- CharacterListFlags.ExpansionTOL,
- HousingFlags.HousingTOL
- ),
- new ExpansionInfo(
- 11,
- "Endless Journey",
- new ClientVersion("7.0.61.0"),
- FeatureFlags.ExpansionEJ,
- CharacterListFlags.ExpansionEJ,
- HousingFlags.HousingEJ
- )
- };
+ var expansion = expansions[i];
+ if (expansion.ClientVersion != null)
+ {
+ Table[i] = new ExpansionInfo(
+ i,
+ expansion.Name,
+ expansion.ClientVersion,
+ expansion.FeatureFlags,
+ expansion.CharacterListFlags,
+ expansion.HousingFlags
+ );
+ }
+ else
+ {
+ Table[i] = new ExpansionInfo(
+ i,
+ expansion.Name,
+ expansion.ClientFlags ?? ClientFlags.None,
+ expansion.FeatureFlags,
+ expansion.CharacterListFlags,
+ expansion.HousingFlags
+ );
+ }
+ }
}
public ExpansionInfo(
@@ -331,4 +282,22 @@ namespace Server
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; }
+ }
}
diff --git a/Projects/Server/Json/Converters/ClientVersionConverter.cs b/Projects/Server/Json/Converters/ClientVersionConverter.cs
new file mode 100644
index 000000000..c1dc8ecb9
--- /dev/null
+++ b/Projects/Server/Json/Converters/ClientVersionConverter.cs
@@ -0,0 +1,37 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: ClientVersionConverter.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Server.Json
+{
+ public class ClientVersionConverter : JsonConverter
+ {
+ public override ClientVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ return new ClientVersion(reader.GetString());
+ }
+
+ throw new JsonException($"Value must be a string");
+ }
+
+ public override void Write(Utf8JsonWriter writer, ClientVersion value, JsonSerializerOptions options) =>
+ writer.WriteStringValue(value.ToString());
+ }
+}
diff --git a/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs
new file mode 100644
index 000000000..3e778d591
--- /dev/null
+++ b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs
@@ -0,0 +1,30 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: ClientVersionConverterFactory.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.Net;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Server.Json
+{
+ public class ClientVersionConverterFactory : JsonConverterFactory
+ {
+ public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(ClientVersion);
+
+ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
+ new ClientVersionConverter();
+ }
+}
diff --git a/Projects/Server/Json/Converters/FlagsConverter.cs b/Projects/Server/Json/Converters/FlagsConverter.cs
new file mode 100644
index 000000000..38584d05a
--- /dev/null
+++ b/Projects/Server/Json/Converters/FlagsConverter.cs
@@ -0,0 +1,149 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: FlagsConverter.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Server.Json
+{
+ public class FlagsConverter : JsonConverter where T : struct, Enum
+ {
+ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var flags = 0ul;
+ var underlyingType = Enum.GetUnderlyingType(typeof(T));
+
+ while (true)
+ {
+ reader.Read();
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException("Invalid Json structure for Flag object");
+ }
+
+ var key = reader.GetString();
+
+ reader.Read();
+
+ if (!reader.GetBoolean() || !Enum.TryParse(key, out var val))
+ {
+ continue;
+ }
+
+ flags |= ConvertToUInt64(underlyingType, val);
+ }
+
+ switch (Type.GetTypeCode(underlyingType))
+ {
+ case TypeCode.SByte:
+ {
+ var num = (sbyte)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.Byte:
+ {
+ var num = (byte)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.Int16:
+ {
+ var num = (short)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.UInt16:
+ {
+ var num = (ushort)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.UInt32:
+ {
+ var num = (uint)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.Int64:
+ {
+ var num = (long)flags;
+ return Unsafe.As(ref num);
+ }
+ case TypeCode.UInt64:
+ {
+ return Unsafe.As(ref flags);
+ }
+ default:
+ {
+ var num = (int)flags;
+ return Unsafe.As(ref num);
+ }
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ var underlyingType = Enum.GetUnderlyingType(typeof(T));
+ var size = GetUnderlyingTypeLength(Type.GetTypeCode(underlyingType)) - 1;
+ var intValue = ConvertToUInt64(underlyingType, value);
+
+ foreach (var flagName in Enum.GetNames(typeof(T)))
+ {
+ var flagValue = Enum.Parse(flagName, false);
+ var flag = ConvertToUInt64(underlyingType, flagValue);
+ if (flag == 0 || (flag & size) == 0)
+ {
+ writer.WriteBoolean(flagName, (intValue & flag) != 0);
+ }
+ }
+
+ writer.WriteEndObject();
+ }
+
+ private static ulong ConvertToUInt64(Type underlyingType, object value) =>
+ Type.GetTypeCode(underlyingType) switch
+ {
+ TypeCode.SByte => (ulong)(sbyte)value,
+ TypeCode.Byte => (byte)value,
+ TypeCode.Int16 => (ulong)(short)value,
+ TypeCode.UInt16 => (ushort)value,
+ TypeCode.Int32 => (ulong)(int)value,
+ TypeCode.UInt32 => (uint)value,
+ TypeCode.Int64 => (ulong)(long)value,
+ TypeCode.UInt64 => (ulong)value,
+ _ => throw new InvalidOperationException()
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong GetUnderlyingTypeLength(TypeCode typeCode) =>
+ typeCode switch
+ {
+ TypeCode.Byte => 8,
+ TypeCode.SByte => 8,
+ TypeCode.Int16 => 16,
+ TypeCode.UInt16 => 16,
+ TypeCode.Char => 16,
+ TypeCode.Int32 => 32,
+ TypeCode.UInt32 => 32,
+ TypeCode.Int64 => 64,
+ TypeCode.UInt64 => 64,
+ _ => 64
+ };
+ }
+}
diff --git a/Projects/Server/Json/JsonConfig.cs b/Projects/Server/Json/JsonConfig.cs
index c44ba874c..163776d84 100644
--- a/Projects/Server/Json/JsonConfig.cs
+++ b/Projects/Server/Json/JsonConfig.cs
@@ -39,6 +39,7 @@ namespace Server.Json
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
+ options.Converters.Add(new ClientVersionConverterFactory());
options.Converters.Add(new GuidConverterFactory());
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new MapConverterFactory());