using System.Collections.Generic; using UnityEngine; using VContainer; using YachtDice.Categories; using YachtDice.Dice; using YachtDice.Game; using YachtDice.Scoring; using YachtDice.Economy; using YachtDice.Shop; using YachtDice.Inventory; using YachtDice.Persistence; using YachtDice.Player; using YachtDice.Modifiers.Definition; using YachtDice.Modifiers.Runtime; namespace YachtDice.UI { public class GameController : MonoBehaviour { [Header("Views")] [SerializeField] private ScoreCardView scoreCardView; [SerializeField] private DicePanelView dicePanelView; [SerializeField] private GameInfoView gameInfoView; [Header("Settings")] [SerializeField] private int maxRollsPerTurn = 3; private const int UpperBonusThreshold = 63; private const int UpperBonusValue = 35; private GameManager gameManager; private ScoringSystem scoringSystem; private DiceManager diceManager; private CurrencyBank currencyBank; private ShopController shopController; private InventoryController inventoryController; private ModifierRegistry modifierRegistry; private CategoryCatalog categoryCatalog; private ModifierCatalog modifierCatalog; private DiceCatalog diceCatalog; private ShopModel shopModel; private PlayerModel playerModel; [Inject] public void Construct( GameManager gameManager, ScoringSystem scoringSystem, DiceManager diceManager, CurrencyBank currencyBank, ShopController shopController, InventoryController inventoryController, ModifierRegistry modifierRegistry, CategoryCatalog categoryCatalog, ModifierCatalog modifierCatalog, DiceCatalog diceCatalog, ShopModel shopModel, PlayerModel playerModel) { this.gameManager = gameManager; this.scoringSystem = scoringSystem; this.diceManager = diceManager; this.currencyBank = currencyBank; this.shopController = shopController; this.inventoryController = inventoryController; this.modifierRegistry = modifierRegistry; this.categoryCatalog = categoryCatalog; this.modifierCatalog = modifierCatalog; this.diceCatalog = diceCatalog; this.shopModel = shopModel; this.playerModel = playerModel; } // ── Lifecycle ────────────────────────────────────────────── private void Start() { // Model → Controller gameManager.OnTurnStarted += HandleTurnStarted; gameManager.OnRollComplete += HandleRollComplete; gameManager.OnScored += HandleScored; gameManager.OnGameOver += HandleGameOver; diceManager.OnDiceSettled += HandleDiceSettled; // View → Controller scoreCardView.OnCategorySelected += HandleCategorySelected; dicePanelView.OnRollClicked += HandleRollClicked; dicePanelView.OnDiceToggled += HandleDiceToggled; gameInfoView.OnNewGameClicked += HandleNewGameClicked; gameInfoView.OnShopClicked += HandleShopClicked; gameInfoView.OnInventoryClicked += HandleInventoryClicked; // Currency & Player state currencyBank.OnBalanceChanged += HandleCurrencyChanged; playerModel.OnChanged += HandlePlayerChangedForSave; // Initialize scoreCardView.Initialize(categoryCatalog); LoadSaveData(); gameInfoView.SetCurrencyText(currencyBank.Balance); // Start the game after all subscriptions are in place gameManager.StartNewGame(); } private void OnDestroy() { gameManager.OnTurnStarted -= HandleTurnStarted; gameManager.OnRollComplete -= HandleRollComplete; gameManager.OnScored -= HandleScored; gameManager.OnGameOver -= HandleGameOver; diceManager.OnDiceSettled -= HandleDiceSettled; scoreCardView.OnCategorySelected -= HandleCategorySelected; dicePanelView.OnRollClicked -= HandleRollClicked; dicePanelView.OnDiceToggled -= HandleDiceToggled; gameInfoView.OnNewGameClicked -= HandleNewGameClicked; gameInfoView.OnShopClicked -= HandleShopClicked; gameInfoView.OnInventoryClicked -= HandleInventoryClicked; currencyBank.OnBalanceChanged -= HandleCurrencyChanged; if (playerModel != null) playerModel.OnChanged -= HandlePlayerChangedForSave; } // ── Save / Load ───────────────────────────────────────── private void LoadSaveData() { SaveData save = SaveSystem.Load(); if (save.Currency > 0) currencyBank.SetBalance(save.Currency); if (modifierCatalog != null && save.OwnedModifiers.Count > 0) { var entries = new List(); var permanentIds = new HashSet(); for (int i = 0; i < save.OwnedModifiers.Count; i++) { var oldEntry = save.OwnedModifiers[i]; var def = modifierCatalog.FindById(oldEntry.ModifierId); if (def == null) { Debug.LogWarning($"Modifier '{oldEntry.ModifierId}' not found in catalog, skipping."); continue; } entries.Add(new ModifierSaveEntry { ModifierId = oldEntry.ModifierId, IsActive = oldEntry.IsActive, RemainingUses = oldEntry.RemainingUses, Stacks = oldEntry.Stacks, CustomState = oldEntry.CustomState, }); if (!def.HasLimitedUses) permanentIds.Add(def.Id); } modifierRegistry.LoadSaveData(entries, modifierCatalog); shopModel.LoadPurchasedPermanentIds(permanentIds); } if (diceCatalog != null && save.OwnedDiceIds != null && save.OwnedDiceIds.Count > 0) { playerModel.Dice.LoadSaveData(save.OwnedDiceIds, diceCatalog); var dicePermIds = new HashSet(save.OwnedDiceIds); var existingIds = shopModel.GetPurchasedPermanentIds(); foreach (var id in dicePermIds) existingIds.Add(id); shopModel.LoadPurchasedPermanentIds(existingIds); } } private void PerformSave() { var save = new SaveData { Currency = currencyBank.Balance, OwnedDiceIds = playerModel.Dice.GetSaveData(), }; var entries = modifierRegistry.GetSaveData(); for (int i = 0; i < entries.Count; i++) { save.OwnedModifiers.Add(entries[i]); } SaveSystem.Save(save); } // ── Model Event Handlers ────────────────────────────────── private void HandleTurnStarted(int turn) { int totalCategoryCount = categoryCatalog.Count; gameInfoView.SetTurnText(turn, totalCategoryCount); dicePanelView.ResetForNewTurn(); dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn); scoreCardView.ClearAllPreviews(); } private void HandleRollComplete(int rollNumber) { bool canRollAgain = gameManager.CanRoll; dicePanelView.SetRollButtonState(canRollAgain, rollNumber, maxRollsPerTurn); dicePanelView.SetDiceInteractable(true); int[] values = diceManager.GetCurrentValues(); dicePanelView.SetAllDiceValues(values); UpdatePreviewScores(); } private void HandleDiceSettled(int index, int value) { dicePanelView.SetDiceValue(index, value); } private void HandleScored(CategoryDefinition category, int finalScore) { scoreCardView.SetCategoryScored(category, finalScore); UpdateTotalDisplay(); PerformSave(); } private void HandleGameOver(int totalScore) { dicePanelView.SetRollButtonState(false, maxRollsPerTurn, maxRollsPerTurn); dicePanelView.SetDiceInteractable(false); scoreCardView.SetAllInteractable(false); int displayTotal = CalculateDisplayTotal(); gameInfoView.ShowGameOver(displayTotal); PerformSave(); } // ── View Event Handlers ─────────────────────────────────── private void HandleRollClicked() { if (!gameManager.CanRoll) return; dicePanelView.SetRollButtonState(false, gameManager.CurrentRoll, maxRollsPerTurn); dicePanelView.SetDiceInteractable(false); scoreCardView.SetAllInteractable(false); gameManager.Roll(); } private void HandleDiceToggled(int index) { if (gameManager.CurrentRoll == 0) return; if (diceManager.IsAnyRolling) return; gameManager.ToggleDiceLock(index); bool isLocked = diceManager.IsLocked(index); dicePanelView.SetDiceLocked(index, isLocked); } private void HandleCategorySelected(CategoryDefinition category) { if (!gameManager.CanScore) return; if (scoringSystem.IsCategoryUsed(category)) return; gameManager.ScoreInCategory(category); } private void HandleNewGameClicked() { gameInfoView.HideGameOver(); scoreCardView.ResetAll(); dicePanelView.ResetForNewGame(); gameManager.StartNewGame(); } private void HandleShopClicked() { shopController.ToggleVisibility(); } private void HandleInventoryClicked() { inventoryController.ToggleVisibility(); } private void HandleCurrencyChanged(int newBalance) { gameInfoView.SetCurrencyText(newBalance); } private void HandlePlayerChangedForSave() { PerformSave(); } // ── Helpers ──────────────────────────────────────────────── private void UpdatePreviewScores() { var dice = diceManager.GetDice(); var previews = new Dictionary(); var allCategories = categoryCatalog.All; for (int i = 0; i < allCategories.Count; i++) { var cat = allCategories[i]; if (scoringSystem.IsCategoryUsed(cat)) continue; ScoreResult result = scoringSystem.PreviewScore(dice, cat); previews[cat] = result.FinalScore; } scoreCardView.UpdatePreviews(previews); } private void UpdateTotalDisplay() { int upperSum = CalculateUpperSum(); bool hasBonus = upperSum >= UpperBonusThreshold; int displayTotal = CalculateDisplayTotal(); scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus); } private int CalculateUpperSum() { int upperSum = 0; var allCategories = categoryCatalog.All; for (int i = 0; i < allCategories.Count; i++) { if (!allCategories[i].IsUpperSection) continue; int catScore = scoringSystem.GetCategoryScore(allCategories[i]); if (catScore >= 0) upperSum += catScore; } return upperSum; } private int CalculateDisplayTotal() { int total = scoringSystem.TotalScore; int upperSum = CalculateUpperSum(); if (upperSum >= UpperBonusThreshold) total += UpperBonusValue; return total; } } }