From 7888445eee15b9e7e1cac0682f2d24daa3991e86 Mon Sep 17 00:00:00 2001 From: Robert Paciorek Date: Sat, 13 Jan 2024 01:04:54 +0000 Subject: [PATCH] support for mods - items add/overwrite interface --- src/Program.cs | 5 ++ src/Schema/ModAction.cs | 17 ++++++ src/Schema/ModItem.cs | 17 ++++++ src/Schema/ModManifest.cs | 9 +++ src/Services/InventoryService.cs | 1 + src/Services/ItemService.cs | 10 +++- src/Services/ModdingService.cs | 94 ++++++++++++++++++++++++++++++++ src/Services/StoreService.cs | 18 ++++-- 8 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 src/Schema/ModAction.cs create mode 100644 src/Schema/ModItem.cs create mode 100644 src/Schema/ModManifest.cs create mode 100644 src/Services/ModdingService.cs diff --git a/src/Program.cs b/src/Program.cs index 0b338b5..75140f9 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -19,6 +19,7 @@ builder.Services.AddControllers(options => { }); builder.Services.AddDbContext(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -44,6 +45,10 @@ using var scope = app.Services.CreateScope(); scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); +// create Modding Service singleton ... do this before start http server to avoid serve invalid assets list + +new ModdingService(); + // Configure the HTTP request pipeline. if (assetServer) diff --git a/src/Schema/ModAction.cs b/src/Schema/ModAction.cs new file mode 100644 index 0000000..42f2ebf --- /dev/null +++ b/src/Schema/ModAction.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public enum ModAction { + [XmlEnum("defualt")] + Default = 0, + + [XmlEnum("add")] + Add = 1, + + [XmlEnum("replace")] + Replace = 2, + + [XmlEnum("remove")] + Remove = 3, +} diff --git a/src/Schema/ModItem.cs b/src/Schema/ModItem.cs new file mode 100644 index 0000000..2f68cac --- /dev/null +++ b/src/Schema/ModItem.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +public class ModItem { + [XmlAttribute("action")] + public ModAction action { get; set; } = ModAction.Default; + + [XmlElement(ElementName = "id")] + public int? ItemID; + + [XmlElement(ElementName = "storeID")] + public int[] stores; + + [XmlElement(ElementName = "data")] + public ItemData data; +} diff --git a/src/Schema/ModManifest.cs b/src/Schema/ModManifest.cs new file mode 100644 index 0000000..a720f11 --- /dev/null +++ b/src/Schema/ModManifest.cs @@ -0,0 +1,9 @@ +using System.Xml.Serialization; + +namespace sodoff.Schema; + +[XmlRoot(ElementName = "sodoffmod", Namespace = "")] +public class ModManifest { + [XmlArrayItem(ElementName = "item")] + public ModItem [] items { get; set; } +} diff --git a/src/Services/InventoryService.cs b/src/Services/InventoryService.cs index ad2c062..663bfa0 100644 --- a/src/Services/InventoryService.cs +++ b/src/Services/InventoryService.cs @@ -120,6 +120,7 @@ namespace sodoff.Services { foreach (InventoryItem item in items) { if (item.Quantity == 0) continue; // Don't include an item that the viking doesn't have ItemData itemData = itemService.GetItem(item.ItemId); + if (itemData is null) continue; // Don't include items removed from item database UserItemData uid = new UserItemData { UserInventoryID = item.Id, ItemID = itemData.ItemID, diff --git a/src/Services/ItemService.cs b/src/Services/ItemService.cs index 38c0e94..1b0ee70 100644 --- a/src/Services/ItemService.cs +++ b/src/Services/ItemService.cs @@ -11,17 +11,23 @@ namespace sodoff.Services { int[] itemsRewardForDT; Random random = new Random(); - public ItemService() { + public ItemService(ModdingService moddingService) { ServerItemArray itemArray = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("items")); foreach (var item in itemArray.ItemDataArray) { items.Add(item.ItemID, item); } itemsRewardForDT = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("dtrewards")); + + moddingService.UpdateItems(ref items); } public ItemData GetItem(int id) { - return items[id]; + try { + return items[id]; + } catch { + return null; + } } public ItemData GetDTReward(Gender gender) { diff --git a/src/Services/ModdingService.cs b/src/Services/ModdingService.cs new file mode 100644 index 0000000..6179745 --- /dev/null +++ b/src/Services/ModdingService.cs @@ -0,0 +1,94 @@ +using sodoff.Schema; +using sodoff.Util; + +using System.Xml.Serialization; + +namespace sodoff.Services; + +public class ModdingService { + + private Dictionary itemsToUpdate = new(); + private Dictionary> itemsInStore = new(); + + public ModdingService() { + if (!Directory.Exists("mods/")) + return; + foreach (var dir in Directory.GetDirectories("mods/")) { + string manifestFile = dir + "/manifest.xml"; + if (File.Exists(manifestFile)) { + Console.WriteLine($"Load mod manifest from {manifestFile}"); + ModManifest modManifest = XmlUtil.DeserializeXml(System.IO.File.ReadAllText(manifestFile)); + + if (modManifest.items != null) { + foreach (ModItem item in modManifest.items) { + // get item id from mod-level info + int? itemID = item.ItemID; + + // process item data itemID value + if (itemID is null) + itemID = item.data?.ItemID; + else if (item.data != null) + item.data.ItemID = (int)itemID; + + // check for unset itemID + if (itemID is null) { + Console.WriteLine("Missing item id."); + System.Environment.Exit(1); + } + + try { + itemsToUpdate.Add((int)itemID, item); + if (item.stores != null && (item.action == ModAction.Add || item.action == ModAction.Default)) { + foreach (int storeID in item.stores) { + try { + itemsInStore[storeID].Add((int)itemID); + } catch (System.Collections.Generic.KeyNotFoundException) { + itemsInStore.Add(storeID, new List {(int)itemID}); + } + } + } + } catch (System.ArgumentException) { + Console.WriteLine($"Conflict for ItemID = {itemID} with previous modification."); + System.Environment.Exit(1); + } + } + } + } else { + Console.WriteLine($"Skip mod directory {dir} (missing manifest file)"); + } + } + } + + public void UpdateItems(ref Dictionary items) { + foreach (var item in itemsToUpdate) { + if (item.Value.action == ModAction.Remove) { + try { + items.Remove(item.Key); + } catch { + Console.WriteLine($"Can't remove item with ID = {item.Key} - not found"); + } + } else if (item.Value.action == ModAction.Replace) { + try { + items[item.Key] = item.Value.data; + } catch { + Console.WriteLine($"Can't replace item with ID = {item.Key} - not found"); + } + } else /*if (item.Value.action == ModAction.Add || item.Value.action == ModAction.Default)*/ { + try { + items.Add(item.Key, item.Value.data); + } catch { + Console.WriteLine($"Item with ID = {item.Key} is already defined in base item set."); + System.Environment.Exit(1); + } + } + } + itemsToUpdate.Clear(); + } + public List GetStoreItem(int storeID) { + if (itemsInStore.ContainsKey(storeID)) { + return itemsInStore[storeID]; + } else { + return new List(); + } + } +} diff --git a/src/Services/StoreService.cs b/src/Services/StoreService.cs index 8994043..7ce615d 100644 --- a/src/Services/StoreService.cs +++ b/src/Services/StoreService.cs @@ -6,23 +6,31 @@ namespace sodoff.Services; public class StoreService { Dictionary stores = new(); - public StoreService(ItemService itemService) { + public StoreService(ItemService itemService, ModdingService moddingService) { StoreData[] storeArray = XmlUtil.DeserializeXml(XmlUtil.ReadResourceXmlString("store")); foreach (var s in storeArray) { - var newStore = new ItemsInStoreData { + ItemsInStoreData newStore = new() { ID = s.Id, StoreName = s.StoreName, Description = s.Description, - Items = new ItemData[s.ItemId.Length], SalesAtStore = s.SalesAtStore, PopularItems = s.PopularItems }; + List itemsList = new(); IEnumerable? memberSales = s.SalesAtStore?.Where(x => x.ForMembers == true); IEnumerable? normalSales = s.SalesAtStore?.Where(x => x.ForMembers == false || x.ForMembers == null); for (int i = 0; i < s.ItemId.Length; ++i) { - newStore.Items[i] = itemService.GetItem(s.ItemId[i]); - UpdateItemSaleModifier(newStore.Items[i], memberSales, normalSales); + ItemData item = itemService.GetItem(s.ItemId[i]); + if (item is null) continue; // skip removed items + itemsList.Add(item); + UpdateItemSaleModifier(item, memberSales, normalSales); } + foreach (int itemID in moddingService.GetStoreItem(s.Id)) { + ItemData item = itemService.GetItem(itemID); + itemsList.Add(item); + UpdateItemSaleModifier(item, memberSales, normalSales); + } + newStore.Items = itemsList.ToArray(); stores.Add(s.Id, newStore); } }