using System; using System.Collections.Generic; using System.Linq; using AYellowpaper.SerializedCollections; using BriarQueen.Data.Identifiers; using BriarQueen.Data.IO.Saves; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.Hints.Data; using BriarQueen.Framework.Managers.Levels.Data; using BriarQueen.Framework.Services.Puzzles.Base; using BriarQueen.Game.Levels.ChapterOne.LaxleyHouse.FireplaceLockbox; using Cysharp.Threading.Tasks; using MemoryPack; using UnityEngine; using UnityEngine.UI; using Random = UnityEngine.Random; namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse { [Serializable] [MemoryPackable] public partial struct FireplaceLockboxPuzzleState { public int SolutionSlotOne; public int SolutionSlotTwo; public int SolutionSlotThree; public int SlotOneValue; public int SlotTwoValue; public int SlotThreeValue; } public class FireplaceLockboxBasePuzzle : BasePuzzle, IPuzzleStateful { private const int MinDigit = 1; private const int MaxDigit = 9; public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.FireplaceLockboxPuzzle]; public override string LevelName => "Fireplace Lockbox"; public override Dictionary Hints { get; } [Header("Components")] [SerializeField] [SerializedDictionary(keyName: "Lockbox Number", valueName: "Number Sprite")] private SerializedDictionary _numberSprites = new(); [Header("Puzzle Slots")] [SerializeField] private List _lockboxSlots = new(); [Header("Level State")] [SerializeField] private Image _backgroundImage; [SerializeField] private Sprite _lockBoxOpenSprite; [SerializeField] private List _lockboxItems = new(); private int _solutionSlotOne = -1; private int _solutionSlotTwo = -1; private int _solutionSlotThree = -1; public bool IsCompleted { get; private set; } private void Awake() { InitializeSlots(); } protected override async UniTask PostLoadInternal() { if (SaveManager.GetLevelFlag(LevelFlag.LaxleyLockboxOpened)) { DisableSlots(); await OpenLockbox(); } } public override async UniTask CompletePuzzle() { if (SaveManager.GetLevelFlag(LevelFlag.LaxleyLockboxOpened)) return; IsCompleted = true; SaveManager.SetLevelFlag(LevelFlag.LaxleyLockboxOpened, true); SaveManager.SetPuzzleCompleted(PuzzleKey.FireplaceLockboxPuzzle, true); DisableSlots(); EventCoordinator.Publish(new FadeEvent(false, 0.5f)); AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.LockboxOpening)); await OpenLockbox(); EventCoordinator.Publish(new FadeEvent(true, 0.5f)); } private async UniTask OpenLockbox() { if (_backgroundImage != null) _backgroundImage.sprite = _lockBoxOpenSprite; await GenerateLockboxItems(); } private async UniTask GenerateLockboxItems() { foreach (var item in _lockboxItems) { if (item == null) continue; if (SaveManager.CurrentSave.CollectedItems.All(x => x.UniqueIdentifier != item.ItemData.UniqueID)) { item.CanvasGroup.alpha = 1f; item.CanvasGroup.blocksRaycasts = true; item.CanvasGroup.interactable = true; } else { await DestructionService.Destroy(item.gameObject); } } } public UniTask CaptureState() { var state = new FireplaceLockboxPuzzleState { SolutionSlotOne = _solutionSlotOne, SolutionSlotTwo = _solutionSlotTwo, SolutionSlotThree = _solutionSlotThree, SlotOneValue = _lockboxSlots.Count > 0 ? _lockboxSlots[0].CurrentValue : MinDigit, SlotTwoValue = _lockboxSlots.Count > 1 ? _lockboxSlots[1].CurrentValue : MinDigit, SlotThreeValue = _lockboxSlots.Count > 2 ? _lockboxSlots[2].CurrentValue : MinDigit }; return UniTask.FromResult(MemoryPackSerializer.Serialize(state)); } public UniTask RestoreState(byte[] state) { if (state == null || state.Length == 0) { GenerateSolutionIfNeeded(); ApplyRandomDefaultSlotValues(); ApplySolutionToCompletionCheck(); if (SaveManager.GetLevelFlag(LevelFlag.LaxleyLockboxOpened)) DisableSlots(); return UniTask.CompletedTask; } var restored = MemoryPackSerializer.Deserialize(state); _solutionSlotOne = WrapDigit(restored.SolutionSlotOne); _solutionSlotTwo = WrapDigit(restored.SolutionSlotTwo); _solutionSlotThree = WrapDigit(restored.SolutionSlotThree); if (_lockboxSlots.Count > 0) _lockboxSlots[0].ApplyValueImmediate(restored.SlotOneValue); if (_lockboxSlots.Count > 1) _lockboxSlots[1].ApplyValueImmediate(restored.SlotTwoValue); if (_lockboxSlots.Count > 2) _lockboxSlots[2].ApplyValueImmediate(restored.SlotThreeValue); ApplySolutionToCompletionCheck(); if (SaveManager.GetLevelFlag(LevelFlag.LaxleyLockboxOpened)) DisableSlots(); return UniTask.CompletedTask; } private void InitializeSlots() { if (_lockboxSlots == null) return; foreach (var slot in _lockboxSlots) { if (slot == null) continue; slot.Initialize(GetNumberSprite, OnSlotValueChanged); } } private void DisableSlots() { if (_lockboxSlots == null) return; foreach (var slot in _lockboxSlots) { if (slot == null) continue; slot.gameObject.SetActive(false); } } private void ApplyRandomDefaultSlotValues() { if (_lockboxSlots.Count > 0) _lockboxSlots[0].ApplyValueImmediate(GetRandomDigit()); if (_lockboxSlots.Count > 1) _lockboxSlots[1].ApplyValueImmediate(GetRandomDigit()); if (_lockboxSlots.Count > 2) _lockboxSlots[2].ApplyValueImmediate(GetRandomDigit()); } private int GetRandomDigit() { return Random.Range(MinDigit, MaxDigit + 1); } private Sprite GetNumberSprite(int value) { value = WrapDigit(value); return _numberSprites.TryGetValue(value, out var sprite) ? sprite : null; } private void OnSlotValueChanged() { ApplySolutionToCompletionCheck(); if (!SaveManager.GetLevelFlag(LevelFlag.LaxleyLockboxOpened) && IsCurrentInputCorrect()) CompletePuzzle().Forget(); } private void ApplySolutionToCompletionCheck() { IsCompleted = IsCurrentInputCorrect(); } private bool IsCurrentInputCorrect() { if (_lockboxSlots == null || _lockboxSlots.Count < 3) return false; return _lockboxSlots[0].CurrentValue == _solutionSlotOne && _lockboxSlots[1].CurrentValue == _solutionSlotTwo && _lockboxSlots[2].CurrentValue == _solutionSlotThree; } private void GenerateSolutionIfNeeded() { if (HasValidSolution()) return; GenerateSolutionVariation(); } private bool HasValidSolution() { return _solutionSlotOne is >= MinDigit and <= MaxDigit && _solutionSlotTwo is >= MinDigit and <= MaxDigit && _solutionSlotThree is >= MinDigit and <= MaxDigit; } private void GenerateSolutionVariation() { int[] digits = { 8, 4, 7 }; for (int i = digits.Length - 1; i > 0; i--) { int swapIndex = Random.Range(0, i + 1); (digits[i], digits[swapIndex]) = (digits[swapIndex], digits[i]); } _solutionSlotOne = digits[0]; _solutionSlotTwo = digits[1]; _solutionSlotThree = digits[2]; } private int WrapDigit(int value) { value %= MaxDigit; if (value <= 0) value += MaxDigit; return value; } } }