Additional Levels added. Mostly just base artwork so far.
This commit is contained in:
@@ -104,8 +104,10 @@ namespace BriarQueen.Data.IO.Saves
|
||||
WorkshopDownstairsDoorOpen,
|
||||
WorkshopDownstairsLightOn,
|
||||
WorkshopGrindstoneRepaired,
|
||||
StreetGateOpen,
|
||||
StreetVinesCut,
|
||||
VillageStreetGateOpen,
|
||||
VillageStreetVinesCut,
|
||||
LaxleyFireplaceExtinguished,
|
||||
LaxleyLockboxOpened,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
||||
@@ -5,5 +5,6 @@ namespace BriarQueen.Data.Identifiers
|
||||
WorkshopSafeUnlocked,
|
||||
WorkshopPuzzleBoxSolved,
|
||||
FountainGemPuzzleSolved,
|
||||
FireplaceLockboxPuzzleBoxSolved,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
None = 0,
|
||||
Village = 1,
|
||||
Workshop = 2
|
||||
Workshop = 2,
|
||||
LaxleyHouse = 3,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,8 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum ToolID
|
||||
{
|
||||
None = 0,
|
||||
Knife = 1
|
||||
Knife = 1,
|
||||
WaterBucket,
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user