Additional Levels added. Mostly just base artwork so far.

This commit is contained in:
2026-03-26 21:31:29 +00:00
parent 644b56282e
commit b3b569e98f
131 changed files with 6151 additions and 6008 deletions

View File

@@ -104,8 +104,10 @@ namespace BriarQueen.Data.IO.Saves
WorkshopDownstairsDoorOpen,
WorkshopDownstairsLightOn,
WorkshopGrindstoneRepaired,
StreetGateOpen,
StreetVinesCut,
VillageStreetGateOpen,
VillageStreetVinesCut,
LaxleyFireplaceExtinguished,
LaxleyLockboxOpened,
}
[Serializable]

View File

@@ -5,5 +5,6 @@ namespace BriarQueen.Data.Identifiers
WorkshopSafeUnlocked,
WorkshopPuzzleBoxSolved,
FountainGemPuzzleSolved,
FireplaceLockboxPuzzleBoxSolved,
}
}

View File

@@ -43,7 +43,17 @@ namespace BriarQueen.Data.Identifiers
ChapterOneWorkshopBookcase,
ChapterOneFountainPuzzle,
ChapterOneStreetGateSign,
ChapterOneStreetCart,
ChapterOneLaxleyHouse,
ChapterOneLaxleyHouseClock,
ChapterOneLaxleyHouseClockPuzzle,
ChapterOneLaxleyHouseTable,
ChapterOneLaxleyHouseUpstairs,
ChapterOneLaxleyHouseFireplace,
ChapterOneVillageMarketSquare,
ChapterOneVillageMarketSquareStatue,
ChapterOneVillageMarketSquareFirepit,
ChapterOneVillageEnd,
ChapterOneVillageEndChurch
}
public enum AssetItemKey

View File

@@ -18,7 +18,9 @@ namespace BriarQueen.Data.Identifiers
GateOpening,
PuzzleIncorrect,
ResetPuzzle,
SharpenKnife
SharpenKnife,
LockBoxNumberReel,
LockboxOpening,
}
public enum UIFXKey
@@ -59,7 +61,8 @@ namespace BriarQueen.Data.Identifiers
{ SFXKey.GateOpening, "SFX_GateOpening" },
{ SFXKey.PuzzleIncorrect, "SFX_PuzzleIncorrect" },
{ SFXKey.ResetPuzzle, "SFX_ResetPuzzle" },
{ SFXKey.SharpenKnife, "SFX_SharpenKnife"}
{ SFXKey.SharpenKnife, "SFX_SharpenKnife"},
{ SFXKey.LockBoxNumberReel, "SFX_LockBoxNumberReel" },
});
public static readonly IReadOnlyDictionary<UIFXKey, string> UIFX =

View File

@@ -6,7 +6,8 @@ namespace BriarQueen.Data.Identifiers
public enum BookEntryID
{
None = 0,
WorkshopDiary = 1
WorkshopDiary = 1,
LaxleyHouseBillOfSale = 2,
}
public enum ClueEntryID
@@ -14,7 +15,8 @@ namespace BriarQueen.Data.Identifiers
None = 0,
WorkshopBookshelfClue = 1,
WorkshopRainbowClue = 2,
PumphouseScratchedTable = 3
PumphouseScratchedTable = 3,
StreetGatePlaque = 4
}
public enum PhotoEntryID
@@ -44,7 +46,9 @@ namespace BriarQueen.Data.Identifiers
new Dictionary<ClueEntryID, string>
{
{ ClueEntryID.WorkshopBookshelfClue, "CLUE_WorkshopBookshelf" },
{ ClueEntryID.WorkshopRainbowClue , "CLUE_WorkshopRainbow" }
{ ClueEntryID.WorkshopRainbowClue , "CLUE_WorkshopRainbow" },
{ ClueEntryID.PumphouseScratchedTable, "CLUE_PumphouseScratchedTable" },
{ ClueEntryID.StreetGatePlaque, "CLUE_StreetGatePlaque" }
});
public static readonly IReadOnlyDictionary<PhotoEntryID, string> Photos =

View File

@@ -15,6 +15,7 @@ namespace BriarQueen.Data.Identifiers
RagFallsApart = 7,
LooksImportant = 8,
WrongTool = 9,
RefillBucket = 10,
}
public enum LevelInteractKey
@@ -47,6 +48,9 @@ namespace BriarQueen.Data.Identifiers
FreshAndCoolWater = 14,
WorkshopBooks = 15,
PumpTurnOn = 16,
FireHot = 17,
ExtinguishFire = 18,
CauldronBoiledAway = 19
}
public enum UIInteractKey
@@ -69,7 +73,8 @@ namespace BriarQueen.Data.Identifiers
{ ItemInteractKey.CarefulInteract, "I need to be careful with this." },
{ ItemInteractKey.RagFallsApart, "The rag fell apart." },
{ ItemInteractKey.LooksImportant, "That looks important." },
{ ItemInteractKey.WrongTool, "I need the proper tool for this."}
{ ItemInteractKey.WrongTool, "I need the proper tool for this."},
{ ItemInteractKey.RefillBucket, "I need to refill this before I can use."}
});
public static readonly IReadOnlyDictionary<LevelInteractKey, string> LevelInteractions =
@@ -103,7 +108,9 @@ namespace BriarQueen.Data.Identifiers
{ EnvironmentInteractKey.SharpGlass, "Ow... that's sharp." },
{ EnvironmentInteractKey.FreshAndCoolWater, "The water feels cool and refreshing." },
{ EnvironmentInteractKey.WorkshopBooks, "The books are ancient and crumbling." },
{ EnvironmentInteractKey.PumpTurnOn, "The water pumps splutter into life."}
{ EnvironmentInteractKey.PumpTurnOn, "The water pumps splutter into life."},
{ EnvironmentInteractKey.FireHot, "I should put the fire out first."},
{ EnvironmentInteractKey.CauldronBoiledAway, "Whatever was in the cauldron, boiled away long ago."}
});
public static readonly IReadOnlyDictionary<UIInteractKey, string> UIInteractions =

View File

@@ -31,6 +31,13 @@ namespace BriarQueen.Data.Identifiers
Diamond = 23,
DiamondTiara = 24,
DustySapphire = 25,
TornPage1 = 26,
TornPage2 = 27,
TornPage3 = 28,
TornPage4 = 29,
TornPage5 = 30,
IncompleteBook = 31,
CompleteBook = 32
}
public enum EnvironmentKey

View File

@@ -4,6 +4,7 @@ namespace BriarQueen.Data.Identifiers
{
None = 0,
Village = 1,
Workshop = 2
Workshop = 2,
LaxleyHouse = 3,
}
}

View File

@@ -7,6 +7,7 @@ namespace BriarQueen.Data.Identifiers
WorkshopCandlePuzzle,
WorkshopPuzzleBox,
FountainGemPuzzle,
FireplaceLockboxPuzzle,
}
public static class PuzzleIdentifiers
@@ -16,6 +17,7 @@ namespace BriarQueen.Data.Identifiers
{ PuzzleKey.WorkshopCandlePuzzle, "CH1:Puzzle:WorkshopCandles" },
{ PuzzleKey.WorkshopPuzzleBox, "CH1:Puzzle:WorkshopBox" },
{ PuzzleKey.FountainGemPuzzle , "CH1:Puzzle:FountainGems" },
{ PuzzleKey.FireplaceLockboxPuzzle, "CH1:Puzzle:FireplaceLockboxPuzzle" },
};
// Optional helper to get all puzzle IDs

View File

@@ -3,6 +3,8 @@ namespace BriarQueen.Data.Identifiers
public enum ToolID
{
None = 0,
Knife = 1
Knife = 1,
WaterBucket,
}
}

View File

@@ -7,6 +7,7 @@ using BriarQueen.Framework.Events.Gameplay;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.UI;
using BriarQueen.Framework.Services.Tutorials;
using NUnit.Framework;
using UnityEngine;
namespace BriarQueen.Framework.Managers.Player.Data.Tools

View File

@@ -14,6 +14,7 @@ using BriarQueen.Framework.Managers.Player.Data.Codex;
using BriarQueen.Framework.Managers.Player.Data.Inventory;
using BriarQueen.Framework.Managers.Player.Data.Tools;
using BriarQueen.Framework.Registries;
using BriarQueen.Framework.Services.Settings;
using BriarQueen.Framework.Services.Tutorials;
using UnityEngine;
using VContainer;
@@ -31,6 +32,7 @@ namespace BriarQueen.Framework.Managers.Player
private readonly SaveManager _saveManager;
private readonly AudioManager _audioManager;
private readonly TutorialService _tutorialService;
private readonly SettingsService _settingsService;
private Backpack _backpack;
private Codex _codex;
@@ -45,7 +47,8 @@ namespace BriarQueen.Framework.Managers.Player
CodexRegistry codexRegistry,
EventCoordinator eventCoordinator,
TutorialService tutorialService,
AudioManager audioManager)
AudioManager audioManager,
SettingsService settingsService)
{
_saveManager = saveManager;
_itemRegistry = itemRegistry;
@@ -53,6 +56,7 @@ namespace BriarQueen.Framework.Managers.Player
_eventCoordinator = eventCoordinator;
_tutorialService = tutorialService;
_audioManager = audioManager;
_settingsService = settingsService;
}
public void Initialize()
@@ -249,6 +253,14 @@ namespace BriarQueen.Framework.Managers.Player
#region Tools
public bool CanUseTool(ToolID id)
{
if(_settingsService.Game.AutoUseTools)
return HasAccessToTool(id);
return GetEquippedTool() == id;
}
public ToolID GetEquippedTool()
{
EnsureToolbelt();

View File

@@ -1,7 +1,70 @@
using BriarQueen.Data.Identifiers;
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace BriarQueen.Game.Items.Environment.General
{
public class Fire
public class Fire : BaseItem
{
[Header("Flags")]
[SerializeField]
private LevelFlag _levelFlag;
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (item != null)
{
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
return;
}
if (!PlayerManager.CanUseTool(ToolID.WaterBucket))
{
PublishFailureMessage();
return;
}
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.ExtinguishFire)));
await Remove();
}
protected override UniTask OnRemoved()
{
SaveManager.SetLevelFlag(_levelFlag, true);
return UniTask.CompletedTask;
}
private void PublishFailureMessage()
{
var autoUseTools = SettingsService != null &&
SettingsService.Game != null &&
SettingsService.Game.AutoUseTools;
var equippedTool = PlayerManager.GetEquippedTool();
string message;
if (equippedTool == ToolID.None)
{
message = InteractEventIDs.Get(EnvironmentInteractKey.FireHot);
}
else if (autoUseTools)
{
// In auto-use mode, reaching this point means the player does not have access
// to the required tool at all, so this should still read like a generic failure.
message = InteractEventIDs.Get(EnvironmentInteractKey.FireHot);
}
else
{
// Auto-use is disabled, and the player has some tool equipped that is not valid.
message = InteractEventIDs.Get(ItemInteractKey.WrongTool);
}
EventCoordinator.Publish(new DisplayInteractEvent(message));
}
}
}

View File

@@ -47,10 +47,7 @@ namespace BriarQueen.Game.Items.Environment.General
private bool CanUseKnife()
{
if (SettingsService.Game.AutoUseTools)
return PlayerManager.HasAccessToTool(ToolID.Knife);
return PlayerManager.GetEquippedTool() == ToolID.Knife;
return PlayerManager.CanUseTool(ToolID.Knife);
}
private void PublishFailureMessage()

View File

@@ -1,7 +1,295 @@
using System;
using System.Threading;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse.FireplaceLockbox
{
public class LockboxSlot
public class LockboxSlot : BaseItem
{
private const int MinDigit = 1;
private const int MaxDigit = 9;
[Header("Components")]
[SerializeField]
private Image _currentNumberImage;
[SerializeField]
private Image _nextNumberImage;
[Header("Animation")]
[SerializeField]
private float _spinPadding = 8f;
[SerializeField]
private float _spinDuration = 0.15f;
[SerializeField]
private Ease _spinEase = Ease.OutCubic;
[SerializeField]
[Range(0f, 1f)]
private float _fadedAlpha = 0.4f;
private RectTransform _currentRect;
private RectTransform _nextRect;
private Sequence _slotSequence;
private CancellationTokenSource _slotCancellationTokenSource;
private Func<int, Sprite> _spriteResolver;
private Action _onValueChanged;
public int CurrentValue { get; private set; } = MinDigit;
public void Initialize(Func<int, Sprite> spriteResolver, Action onValueChanged)
{
_spriteResolver = spriteResolver;
_onValueChanged = onValueChanged;
_currentRect = _currentNumberImage != null ? _currentNumberImage.rectTransform : null;
_nextRect = _nextNumberImage != null ? _nextNumberImage.rectTransform : null;
SyncHelperImageLayout();
ApplyValueImmediate(CurrentValue);
}
public void ApplyValueImmediate(int value)
{
CurrentValue = WrapValue(value);
CancelTweenIfRunning();
SyncHelperImageLayout();
var sprite = ResolveSprite(CurrentValue);
if (_currentNumberImage != null)
{
_currentNumberImage.sprite = sprite;
_currentNumberImage.enabled = sprite != null;
_currentNumberImage.color = Color.white;
}
if (_nextNumberImage != null)
{
_nextNumberImage.sprite = null;
_nextNumberImage.enabled = false;
_nextNumberImage.color = Color.white;
}
if (_currentRect != null)
_currentRect.anchoredPosition = Vector2.zero;
if (_nextRect != null)
_nextRect.anchoredPosition = GetHiddenPosition();
}
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (item != null)
return;
await SpinToNextValue();
}
public async UniTask SpinToNextValue()
{
if (_currentNumberImage == null || _nextNumberImage == null || _currentRect == null || _nextRect == null)
return;
int nextValue = WrapValue(CurrentValue + 1);
var nextSprite = ResolveSprite(nextValue);
if (nextSprite == null)
{
Debug.LogWarning($"[LockboxSlot] Missing sprite for value {nextValue}.", this);
return;
}
CancelTweenIfRunning();
SyncHelperImageLayout();
_slotCancellationTokenSource = new CancellationTokenSource();
_nextNumberImage.sprite = nextSprite;
_nextNumberImage.enabled = true;
_nextNumberImage.color = new Color(1f, 1f, 1f, _fadedAlpha);
_currentNumberImage.enabled = true;
_currentNumberImage.color = Color.white;
_currentRect.anchoredPosition = Vector2.zero;
_nextRect.anchoredPosition = GetHiddenPosition();
_slotSequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.UIAnchoredPosition(
_currentRect,
GetVisibleExitPosition(),
_spinDuration,
_spinEase,
useUnscaledTime: true))
.Group(Tween.UIAnchoredPosition(
_nextRect,
Vector2.zero,
_spinDuration,
_spinEase,
useUnscaledTime: true))
.Group(Tween.Alpha(
_currentNumberImage,
new TweenSettings<float>
{
endValue = _fadedAlpha,
settings = new TweenSettings
{
duration = _spinDuration,
useUnscaledTime = true
}
}))
.Group(Tween.Alpha(
_nextNumberImage,
new TweenSettings<float>
{
endValue = 1f,
settings = new TweenSettings
{
duration = _spinDuration,
useUnscaledTime = true
}
}));
try
{
await _slotSequence.ToUniTask(cancellationToken: _slotCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
if (_slotSequence.isAlive)
_slotSequence.Stop();
_slotSequence = default;
_slotCancellationTokenSource?.Dispose();
_slotCancellationTokenSource = null;
}
SwapImages();
CurrentValue = nextValue;
PrepareHiddenImage();
_onValueChanged?.Invoke();
}
private Sprite ResolveSprite(int value)
{
return _spriteResolver?.Invoke(WrapValue(value));
}
private int WrapValue(int value)
{
value %= MaxDigit;
if (value <= 0)
value += MaxDigit;
return value;
}
private void SwapImages()
{
(_currentNumberImage, _nextNumberImage) = (_nextNumberImage, _currentNumberImage);
(_currentRect, _nextRect) = (_nextRect, _currentRect);
}
private void PrepareHiddenImage()
{
if (_currentNumberImage != null)
{
_currentNumberImage.enabled = true;
_currentNumberImage.color = Color.white;
}
if (_currentRect != null)
_currentRect.anchoredPosition = Vector2.zero;
if (_nextNumberImage != null)
{
_nextNumberImage.sprite = null;
_nextNumberImage.enabled = false;
_nextNumberImage.color = Color.white;
}
if (_nextRect != null)
_nextRect.anchoredPosition = GetHiddenPosition();
}
private void SyncHelperImageLayout()
{
if (_currentRect == null || _nextRect == null)
return;
_nextRect.anchorMin = _currentRect.anchorMin;
_nextRect.anchorMax = _currentRect.anchorMax;
_nextRect.pivot = _currentRect.pivot;
_nextRect.sizeDelta = _currentRect.sizeDelta;
_nextRect.localScale = _currentRect.localScale;
_nextRect.localRotation = _currentRect.localRotation;
}
private Vector2 GetHiddenPosition()
{
float travelDistance = GetTravelDistance();
return new Vector2(0f, -travelDistance);
}
private Vector2 GetVisibleExitPosition()
{
float travelDistance = GetTravelDistance();
return new Vector2(0f, travelDistance);
}
private float GetTravelDistance()
{
if (_currentRect == null)
return _spinPadding;
float rectHeight = _currentRect.rect.height;
if (rectHeight <= 0f && _currentNumberImage != null)
rectHeight = _currentNumberImage.preferredHeight;
return rectHeight + _spinPadding;
}
private void CancelTweenIfRunning()
{
if (_slotCancellationTokenSource != null && !_slotCancellationTokenSource.IsCancellationRequested)
_slotCancellationTokenSource.Cancel();
if (_slotSequence.isAlive)
_slotSequence.Stop();
_slotSequence = default;
_slotCancellationTokenSource?.Dispose();
_slotCancellationTokenSource = null;
}
private void OnDisable()
{
CancelTweenIfRunning();
}
private void OnDestroy()
{
CancelTweenIfRunning();
}
}
}

View File

@@ -1,7 +1,295 @@
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
{
public class FireplaceLockbox
[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<int, BaseHint> Hints { get; }
[Header("Components")]
[SerializeField]
[SerializedDictionary(keyName: "Lockbox Number", valueName: "Number Sprite")]
private SerializedDictionary<int, Sprite> _numberSprites = new();
[Header("Puzzle Slots")]
[SerializeField]
private List<LockboxSlot> _lockboxSlots = new();
[Header("Level State")]
[SerializeField]
private Image _backgroundImage;
[SerializeField]
private Sprite _lockBoxOpenSprite;
[SerializeField]
private List<BaseItem> _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<byte[]> 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<FireplaceLockboxPuzzleState>(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;
}
}
}

View File

@@ -1,7 +1,40 @@
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Game.Items.Environment.General;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse
{
public class LaxleyFireplace
public class LaxleyFireplace : BaseLevel
{
[Header("Fireplace")]
[SerializeField]
private Image _background;
[SerializeField]
private Sprite _firePlaceExtinguishedSprite;
[SerializeField]
private Fire _fireplaceFire;
protected override async UniTask PostLoadInternal()
{
bool fireExtinguished = SaveManager.GetLevelFlag(LevelFlag.LaxleyFireplaceExtinguished);
if (fireExtinguished)
{
await ExtinguishFire();
}
}
private async UniTask ExtinguishFire()
{
_background.sprite = _firePlaceExtinguishedSprite;
await DestructionService.Destroy(_fireplaceFire.gameObject);
}
}
}

View File

@@ -26,6 +26,7 @@ namespace BriarQueen.Game.Levels.ChapterOne.VillageStreet
[Header("Zones")]
[SerializeField]
private StreetVines _vinesZone;
[SerializeField]
private InteractZone _plaqueZone;
@@ -35,8 +36,8 @@ namespace BriarQueen.Game.Levels.ChapterOne.VillageStreet
protected override UniTask PostLoadInternal()
{
bool gateOpen = SaveManager.GetLevelFlag(LevelFlag.StreetGateOpen);
bool vinesCut = SaveManager.GetLevelFlag(LevelFlag.StreetVinesCut) || gateOpen;
bool gateOpen = SaveManager.GetLevelFlag(LevelFlag.VillageStreetGateOpen);
bool vinesCut = SaveManager.GetLevelFlag(LevelFlag.VillageStreetVinesCut) || gateOpen;
if (gateOpen)
{
@@ -57,11 +58,12 @@ namespace BriarQueen.Game.Levels.ChapterOne.VillageStreet
await UniTask.Delay(TimeSpan.FromSeconds(1));
EventCoordinator.Publish(new FadeEvent(false, 0.8f));
await UniTask.Delay(TimeSpan.FromSeconds(0.8f));
_background.sprite = _vinesCutSprite;
SetZoneState(_plaqueZone, true);
SaveManager.SetLevelFlag(LevelFlag.StreetVinesCut, true);
await DeactivateVines();
SaveManager.SetLevelFlag(LevelFlag.VillageStreetVinesCut, true);
EventCoordinator.Publish(new FadeEvent(true, 0.8f));
}
@@ -69,9 +71,9 @@ namespace BriarQueen.Game.Levels.ChapterOne.VillageStreet
{
SetZoneState(_plaqueZone, vinesCut);
SetZoneState(_gateZone, gateOpen);
if(vinesCut)
DeactivateVines();
if (vinesCut)
DeactivateVines().Forget();
}
private void SetZoneState(InteractZone zone, bool active)
@@ -84,16 +86,9 @@ namespace BriarQueen.Game.Levels.ChapterOne.VillageStreet
zone.CanvasGroup.interactable = active;
}
private void DeactivateVines()
private async UniTask DeactivateVines()
{
var canvasGroup = _vinesZone.CanvasGroup;
if (!canvasGroup)
return;
canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
canvasGroup.interactable = false;
await DestructionService.Destroy(_vinesZone.gameObject);
}
}
}

View File

@@ -91,7 +91,7 @@ namespace BriarQueen.Game.Puzzles.ChapterOne.Fountain
_isCompleted = true;
SaveManager.SetLevelFlag(LevelFlag.StreetGateOpen, true);
SaveManager.SetLevelFlag(LevelFlag.VillageStreetGateOpen, true);
SaveManager.SetPuzzleCompleted(PuzzleKey.FountainGemPuzzle, true);
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.GateOpening));
EventCoordinator.Publish(new UnlockAchievementEvent(AchievementID.FountainGemPuzzleSolved));
@@ -118,7 +118,7 @@ namespace BriarQueen.Game.Puzzles.ChapterOne.Fountain
if (state == null || state.Length == 0)
{
_isCompleted = SaveManager.GetLevelFlag(LevelFlag.StreetGateOpen);
_isCompleted = SaveManager.GetLevelFlag(LevelFlag.VillageStreetGateOpen);
return UniTask.CompletedTask;
}
@@ -131,7 +131,7 @@ namespace BriarQueen.Game.Puzzles.ChapterOne.Fountain
if (_isCompleted)
{
SaveManager.SetLevelFlag(LevelFlag.StreetGateOpen, true, false);
SaveManager.SetLevelFlag(LevelFlag.VillageStreetGateOpen, true, false);
SaveManager.SetPuzzleCompleted(PuzzleKey.FountainGemPuzzle, true, false);
}