using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Caching; using PhoenixLib.Logging; using WingsEmu.DTOs.Recipes; using WingsEmu.Game; using WingsEmu.Game.Items; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Recipes; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; public class RecipeManager : IRecipeManager { private readonly IEnumerable _files; private readonly HashSet _generalRecipes = new(); private readonly IItemsManager _itemsManager; private readonly IRecipeFactory _recipeFactory; private readonly IKeyValueCache> _recipes; public RecipeManager(IEnumerable files, IRecipeFactory recipeFactory, IKeyValueCache> recipes, IItemsManager itemsManager) { _files = files; _recipes = recipes; _itemsManager = itemsManager; _recipeFactory = recipeFactory; } public async Task InitializeAsync() { List recipes = null; bool dbFirst = ParserDataPostgresReader.DbFirstEnabled; bool strictDbOnly = ParserDataPostgresReader.StrictDbOnlyEnabled; if (dbFirst) { try { recipes = ParserDataPostgresReader.LoadRecipes(); Log.Info($"[DB_FIRST] Loaded {recipes.Count} recipes from database"); } catch (Exception e) { if (strictDbOnly) { throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but failed to load recipes from database.", e); } Log.Error("[DB_FIRST] Could not load recipes from database", e); } if (strictDbOnly && (recipes == null || recipes.Count == 0)) { throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no recipes were loaded from database."); } } if (recipes == null || recipes.Count == 0) { recipes = new List(); foreach (RecipeObject recipeObject in _files.SelectMany(x => x.Recipes)) { if (recipeObject == null) { continue; } RecipeDTO recipe = recipeObject.ToDto(); IGameItem producerItem = _itemsManager.GetItem(recipe.ProducedItemVnum); if (producerItem is null) { Log.Warn("[RECIPE] Item not found: " + recipe.Id + $" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}"); } List items = new(); if (recipeObject.Items != null) { short slot = 0; foreach (RecipeItemObject recipeItem in recipeObject.Items) { if (recipeItem == null) { continue; } IGameItem item = _itemsManager.GetItem(recipeItem.ItemVnum); if (item is null) { Log.Warn("[RECIPE] Item not found: " + recipeItem.ItemVnum + $" on recipe ProducerItemVnum: {recipe.ProducerItemVnum} | ProducerNpc: {recipe.ProducerNpcVnum} | Producer: {recipe.ProducerMapNpcId}"); continue; } items.Add(recipeItem.ToDto(slot)); slot++; } } recipe.Items = items; recipes.Add(recipe); } ParserDataPostgresSync.SyncRecipes(recipes); } int count = 0; foreach (RecipeDTO recipe in recipes) { if (recipe.Items == null) { continue; } Recipe gameRecipe = _recipeFactory.CreateRecipe(recipe); _generalRecipes.Add(gameRecipe); count++; if (gameRecipe.ProducerItemVnum.HasValue) { string key = $"item-{gameRecipe.ProducerItemVnum.Value}"; List list = _recipes.Get(key); if (list == null) { list = new List(); _recipes.Set(key, list); } list.Add(gameRecipe); continue; } if (gameRecipe.ProducerNpcVnum.HasValue) { string key = $"npcVnum-{gameRecipe.ProducerNpcVnum.Value}"; List list = _recipes.Get(key); if (list == null) { list = new List(); _recipes.Set(key, list); } list.Add(gameRecipe); continue; } if (gameRecipe.ProducerMapNpcId.HasValue) { string key = $"mapNpc-{gameRecipe.ProducerMapNpcId.Value}"; List list = _recipes.Get(key); if (list == null) { list = new List(); _recipes.Set(key, list); } list.Add(gameRecipe); } } Log.Info($"[RECIPE_MANAGER] Loaded {count.ToString()} recipes"); } public IReadOnlyList GetRecipesByProducerItemVnum(int itemVnum) => _recipes.Get($"item-{itemVnum}"); public IReadOnlyList GetRecipesByNpcId(long mapNpcId) => _recipes.Get($"mapNpc-{mapNpcId}"); public IReadOnlyList GetRecipesByNpcMonsterVnum(int npcVNum) => _recipes.Get($"npcVnum-{npcVNum}"); public IReadOnlyList GetRecipeByProducedItemVnum(int itemVnum) => _generalRecipes.Where(x => x != null && x.ProducedItemVnum == itemVnum).ToList(); }