using OpenTheBox.Core; using OpenTheBox.Core.Enums; using OpenTheBox.Core.Items; using OpenTheBox.Data; using OpenTheBox.Simulation.Actions; using OpenTheBox.Simulation.Events; namespace OpenTheBox.Simulation; /// /// The BLACK BOX. Zero I/O. Central orchestrator for all game logic. /// All game actions are processed through this class, which dispatches to the appropriate /// engine and runs post-processing passes (auto-activation, meta unlocks). /// public class GameSimulation { private readonly ContentRegistry _registry; private readonly Random _rng; private readonly BoxEngine _boxEngine; private readonly InteractionEngine _interactionEngine; private readonly MetaEngine _metaEngine; private readonly ResourceEngine _resourceEngine; private readonly CraftingEngine _craftingEngine; public GameSimulation(ContentRegistry registry, Random? rng = null) { _registry = registry; _rng = rng ?? new Random(); _boxEngine = new BoxEngine(registry); _interactionEngine = new InteractionEngine(registry); _metaEngine = new MetaEngine(); _resourceEngine = new ResourceEngine(); _craftingEngine = new CraftingEngine(); } /// /// Processes a game action against the current state, returning all resulting events in order. /// This is the single entry point for all game logic. /// public List ProcessAction(GameAction action, GameState state) { var events = new List(); switch (action) { case OpenBoxAction openBox: events.AddRange(HandleOpenBox(openBox, state)); break; case UseItemAction useItem: events.AddRange(HandleUseItem(useItem, state)); break; case CraftAction craft: events.AddRange(HandleCraft(craft, state)); break; case StartAdventureAction startAdventure: events.AddRange(HandleStartAdventure(startAdventure, state)); break; case ChangeLocaleAction changeLocale: events.AddRange(HandleChangeLocale(changeLocale, state)); break; case EquipCosmeticAction equipCosmetic: events.AddRange(HandleEquipCosmetic(equipCosmetic, state)); break; case SaveGameAction: // Save is handled externally; simulation just acknowledges events.Add(new MessageEvent("save.acknowledged")); break; } return events; } private List HandleOpenBox(OpenBoxAction action, GameState state) { var events = new List(); // Find the box item in inventory var boxItem = state.Inventory.FirstOrDefault(i => i.Id == action.BoxInstanceId); if (boxItem is null) { events.Add(new MessageEvent("error.item_not_found")); return events; } // Consume the box item state.RemoveItem(boxItem.Id); events.Add(new ItemConsumedEvent(boxItem.Id)); // Open the box var boxEvents = _boxEngine.Open(boxItem.DefinitionId, state, _rng); events.AddRange(boxEvents); state.TotalBoxesOpened++; // Collect newly received items for post-processing var newItems = boxEvents.OfType().Select(e => e.Item).ToList(); // Run auto-activation pass events.AddRange(_interactionEngine.CheckAutoActivations(newItems, state)); // Run meta pass events.AddRange(_metaEngine.ProcessNewItems(newItems, state, _registry)); // Run crafting auto-check (new materials may trigger recipes) events.AddRange(_craftingEngine.AutoCraftCheck(state, _registry)); return events; } private List HandleUseItem(UseItemAction action, GameState state) { var events = new List(); var item = state.Inventory.FirstOrDefault(i => i.Id == action.ItemInstanceId); if (item is null) { events.Add(new MessageEvent("error.item_not_found")); return events; } var itemDef = _registry.GetItem(item.DefinitionId); if (itemDef is null) { events.Add(new MessageEvent("error.definition_not_found")); return events; } // Check if it's a consumable resource item if (itemDef.ResourceType.HasValue) { events.AddRange(_resourceEngine.ProcessConsumable(item, state, _registry)); return events; } // Otherwise, check interaction rules var interactionEvents = _interactionEngine.CheckAutoActivations([item], state); events.AddRange(interactionEvents); return events; } private List HandleCraft(CraftAction action, GameState state) { // Crafting is now automatic — delegate to the CraftingEngine return _craftingEngine.AutoCraftCheck(state, _registry); } private List HandleStartAdventure(StartAdventureAction action, GameState state) { var events = new List(); if (!state.UnlockedAdventures.Contains(action.Theme)) { events.Add(new MessageEvent("error.adventure_locked")); return events; } events.Add(new AdventureStartedEvent(action.Theme)); return events; } private static List HandleChangeLocale(ChangeLocaleAction action, GameState state) { var events = new List(); state.CurrentLocale = action.NewLocale; events.Add(new MessageEvent("locale.changed", [action.NewLocale.ToString()])); return events; } private List HandleEquipCosmetic(EquipCosmeticAction action, GameState state) { var events = new List(); var item = state.Inventory.FirstOrDefault(i => i.Id == action.ItemInstanceId); if (item is null) { events.Add(new MessageEvent("error.item_not_found")); return events; } var itemDef = _registry.GetItem(item.DefinitionId); if (itemDef?.CosmeticSlot is null || itemDef.CosmeticValue is null) { events.Add(new MessageEvent("error.not_a_cosmetic")); return events; } var slot = itemDef.CosmeticSlot.Value; var value = itemDef.CosmeticValue; if (!state.Appearance.ApplyCosmetic(slot, value)) { events.Add(new MessageEvent("error.cosmetic_apply_failed")); return events; } events.Add(new CosmeticEquippedEvent(slot, value)); return events; } }