First commit for private source control. Older commits available on Github.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 687d55dc611140ada4fd8af70e83d7d9
|
||||
timeCreated: 1773330168
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BriarQueen.Game.Puzzles.ChapterOne.Workshop.BoxPuzzle
|
||||
{
|
||||
public class PuzzleBoxSlot : BaseItem
|
||||
{
|
||||
[SerializeField]
|
||||
private WorkshopBasePuzzleBox _owner;
|
||||
|
||||
[SerializeField]
|
||||
private Image _pictureImage;
|
||||
|
||||
[Header("Rules")]
|
||||
[SerializeField]
|
||||
private AssetItemKey _requiredPicturePieceID;
|
||||
|
||||
[SerializeField]
|
||||
private int _slotID;
|
||||
|
||||
private CancellationTokenSource _selectedCts;
|
||||
|
||||
private Sequence _selectedSequence;
|
||||
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
|
||||
|
||||
public override string InteractableName => IsLocked ? string.Empty : "Loose Piece";
|
||||
public AssetItemKey RequiredPieceID => _requiredPicturePieceID;
|
||||
|
||||
public bool IsLocked { get; private set; }
|
||||
public AssetItemKey CurrentPieceID { get; private set; }
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (IsLocked)
|
||||
return;
|
||||
|
||||
if(!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (_owner.SelectedPiece == null)
|
||||
{
|
||||
_owner.SelectPiece(this);
|
||||
return;
|
||||
}
|
||||
|
||||
await _owner.TrySwap(this);
|
||||
}
|
||||
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
if (selected)
|
||||
StartBreathingAsync().Forget();
|
||||
else
|
||||
StopBreathing();
|
||||
}
|
||||
|
||||
public bool IsCorrect()
|
||||
{
|
||||
return CurrentPieceID == _requiredPicturePieceID;
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
IsLocked = true;
|
||||
StopBreathing();
|
||||
}
|
||||
|
||||
private async UniTask StartBreathingAsync()
|
||||
{
|
||||
StopBreathing();
|
||||
|
||||
// Create a sequence with scale + alpha pulsing
|
||||
_selectedSequence = Sequence.Create(cycleMode: Sequence.SequenceCycleMode.Yoyo, cycles: -1)
|
||||
.Group(
|
||||
Tween.Scale(_pictureImage.rectTransform, new TweenSettings<Vector3>
|
||||
{
|
||||
startValue = _pictureImage.rectTransform.localScale,
|
||||
endValue = _pictureImage.rectTransform.localScale * 1.05f,
|
||||
settings = new TweenSettings { duration = 0.5f, ease = Ease.InOutSine }
|
||||
}).Group(
|
||||
Tween.Alpha(_pictureImage, new TweenSettings<float>
|
||||
{
|
||||
startValue = 1f,
|
||||
endValue = 0.8f,
|
||||
settings = new TweenSettings { duration = 0.5f, ease = Ease.InOutSine }
|
||||
}))
|
||||
);
|
||||
|
||||
_selectedCts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
await _selectedSequence.ToUniTask(cancellationToken: _selectedCts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when stopping
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCts();
|
||||
}
|
||||
}
|
||||
|
||||
private void StopBreathing()
|
||||
{
|
||||
if (_selectedSequence.isAlive)
|
||||
_selectedSequence.Complete();
|
||||
|
||||
_pictureImage.rectTransform.localScale = Vector3.one;
|
||||
_pictureImage.color = new Color(_pictureImage.color.r, _pictureImage.color.g, _pictureImage.color.b, 1f);
|
||||
DisposeCts();
|
||||
}
|
||||
|
||||
private void DisposeCts()
|
||||
{
|
||||
if (_selectedCts == null) return;
|
||||
|
||||
_selectedCts.Cancel();
|
||||
_selectedCts.Dispose();
|
||||
_selectedCts = null;
|
||||
}
|
||||
|
||||
public async UniTask SetPiece(AssetItemKey pieceID, bool animate)
|
||||
{
|
||||
CurrentPieceID = pieceID;
|
||||
|
||||
var sprite = await _owner.GetSpriteForPiece(AssetKeyIdentifiers.Get(pieceID));
|
||||
if (sprite == null) return;
|
||||
|
||||
if (!animate)
|
||||
{
|
||||
_pictureImage.sprite = sprite;
|
||||
return;
|
||||
}
|
||||
|
||||
await Tween.Alpha(_pictureImage, 0f, 0.5f).ToUniTask();
|
||||
_pictureImage.sprite = sprite;
|
||||
await Tween.Alpha(_pictureImage, 1f, 0.5f).ToUniTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e93f8f355d8249f099cd3d446b40890a
|
||||
timeCreated: 1773335140
|
||||
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.Progression;
|
||||
using BriarQueen.Framework.Managers.Hints.Data;
|
||||
using BriarQueen.Framework.Services.Puzzles.Base;
|
||||
using BriarQueen.Game.Items.HoverZones.ChapterOne.Workshop.Upstairs.PuzzleBox;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using MemoryPack;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace BriarQueen.Game.Puzzles.ChapterOne.Workshop.BoxPuzzle
|
||||
{
|
||||
[Serializable]
|
||||
[MemoryPackable]
|
||||
public partial struct WorkshopPuzzleBoxState
|
||||
{
|
||||
public AssetItemKey[] SlotPieceIDs;
|
||||
public bool Completed;
|
||||
public bool SideEffectsFired;
|
||||
}
|
||||
|
||||
public class WorkshopBasePuzzleBox : BasePuzzle, IPuzzleStateful
|
||||
{
|
||||
private static readonly HashSet<AssetItemKey> PuzzlePieceIDs =
|
||||
Enum.GetValues(typeof(AssetItemKey))
|
||||
.Cast<AssetItemKey>()
|
||||
.Where(k => k.ToString().StartsWith("ChapterOneBoxPuzzlePiece"))
|
||||
.ToHashSet();
|
||||
|
||||
[Header("Puzzle Setup")]
|
||||
[SerializeField]
|
||||
private CanvasGroup _openBoxCanvasGroup;
|
||||
|
||||
[Header("Slots")]
|
||||
[SerializeField]
|
||||
private List<PuzzleBoxSlot> _slots;
|
||||
|
||||
[SerializeField]
|
||||
private bool _lockSlotsOnComplete = true;
|
||||
|
||||
private readonly Dictionary<string, Sprite> _spriteCache = new();
|
||||
|
||||
private bool _restoredFromSave;
|
||||
private bool _sideEffectsFired;
|
||||
|
||||
public override string LevelName => "Puzzle Box";
|
||||
public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.WorkshopPuzzleBox];
|
||||
|
||||
public override Dictionary<int, BaseHint> Hints { get; }
|
||||
|
||||
public PuzzleBoxSlot SelectedPiece { get; private set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public UniTask<byte[]> CaptureState()
|
||||
{
|
||||
var state = new WorkshopPuzzleBoxState
|
||||
{
|
||||
SlotPieceIDs = new AssetItemKey[_slots.Count],
|
||||
Completed = IsCompleted,
|
||||
SideEffectsFired = _sideEffectsFired
|
||||
};
|
||||
|
||||
for (var i = 0; i < _slots.Count; i++)
|
||||
state.SlotPieceIDs[i] = _slots[i].CurrentPieceID;
|
||||
|
||||
var payload = MemoryPackSerializer.Serialize(state);
|
||||
return UniTask.FromResult(payload);
|
||||
}
|
||||
|
||||
public async UniTask RestoreState(byte[] stateBlob)
|
||||
{
|
||||
Debug.Log($"[WorkshopBasePuzzleBox] Restoring state: {stateBlob}");
|
||||
|
||||
if (stateBlob == null || stateBlob.Length == 0)
|
||||
{
|
||||
await ShufflePieces();
|
||||
UpdateInteractionLayers();
|
||||
return;
|
||||
}
|
||||
|
||||
var state = MemoryPackSerializer.Deserialize<WorkshopPuzzleBoxState>(stateBlob);
|
||||
|
||||
for (var i = 0; i < state.SlotPieceIDs.Length; i++)
|
||||
await _slots[i].SetPiece(state.SlotPieceIDs[i], false);
|
||||
|
||||
IsCompleted = state.Completed;
|
||||
_sideEffectsFired = state.SideEffectsFired;
|
||||
|
||||
if (IsCompleted && _lockSlotsOnComplete)
|
||||
{
|
||||
foreach (var slot in _slots)
|
||||
slot.Lock();
|
||||
}
|
||||
|
||||
if (IsCompleted)
|
||||
{
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopDownstairsDoorOpen, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopPuzzleBox, true, false);
|
||||
}
|
||||
|
||||
UpdateInteractionLayers();
|
||||
|
||||
_restoredFromSave = true;
|
||||
}
|
||||
|
||||
private void UpdateInteractionLayers()
|
||||
{
|
||||
var openEnabled = IsCompleted;
|
||||
|
||||
if (_openBoxCanvasGroup != null)
|
||||
{
|
||||
_openBoxCanvasGroup.interactable = openEnabled;
|
||||
_openBoxCanvasGroup.blocksRaycasts = openEnabled;
|
||||
|
||||
if (openEnabled)
|
||||
_openBoxCanvasGroup.GetComponent<PuzzleBoxInteractZone>().SetInteractableName();
|
||||
}
|
||||
|
||||
foreach (var slot in _slots)
|
||||
{
|
||||
var group = slot.CanvasGroup;
|
||||
if (group == null)
|
||||
continue;
|
||||
|
||||
group.interactable = !openEnabled;
|
||||
group.blocksRaycasts = !openEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask<Sprite> GetSpriteForPiece(string pieceID)
|
||||
{
|
||||
if (_spriteCache.TryGetValue(pieceID, out var sprite))
|
||||
return sprite;
|
||||
|
||||
if (!AssetRegistry.TryGetReference(pieceID, out var reference))
|
||||
{
|
||||
Debug.LogError($"[BoxPuzzle] Missing asset reference for {pieceID}");
|
||||
return null;
|
||||
}
|
||||
|
||||
sprite = await AddressableManager.LoadAssetAsync<Sprite>(reference);
|
||||
|
||||
if (sprite != null)
|
||||
_spriteCache[pieceID] = sprite;
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
public void SelectPiece(PuzzleBoxSlot slot)
|
||||
{
|
||||
if (slot == null)
|
||||
return;
|
||||
|
||||
if (SelectedPiece != null && SelectedPiece != slot)
|
||||
SelectedPiece.SetSelected(false);
|
||||
|
||||
SelectedPiece = slot;
|
||||
slot.SetSelected(true);
|
||||
}
|
||||
|
||||
public async UniTask TrySwap(PuzzleBoxSlot other)
|
||||
{
|
||||
if (SelectedPiece == null || other == null)
|
||||
return;
|
||||
|
||||
var first = SelectedPiece;
|
||||
var second = other;
|
||||
|
||||
SelectedPiece = null;
|
||||
|
||||
first.SetSelected(false);
|
||||
second.SetSelected(false);
|
||||
|
||||
if (first == second || first.IsLocked || second.IsLocked)
|
||||
return;
|
||||
|
||||
await SwapSlots(first, second);
|
||||
|
||||
EvaluateLocks();
|
||||
|
||||
if (CheckCompleted() && !IsCompleted)
|
||||
{
|
||||
MarkAsCompleted();
|
||||
await CompletePuzzle();
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SwapSlots(PuzzleBoxSlot a, PuzzleBoxSlot b)
|
||||
{
|
||||
var pieceA = a.CurrentPieceID;
|
||||
var pieceB = b.CurrentPieceID;
|
||||
|
||||
await UniTask.WhenAll(
|
||||
a.SetPiece(pieceB, true),
|
||||
b.SetPiece(pieceA, true)
|
||||
);
|
||||
}
|
||||
|
||||
private async UniTask ShufflePieces()
|
||||
{
|
||||
var ids = PuzzlePieceIDs.ToList();
|
||||
var validShuffle = false;
|
||||
|
||||
while (!validShuffle)
|
||||
{
|
||||
for (var i = 0; i < ids.Count; i++)
|
||||
{
|
||||
var rand = Random.Range(i, ids.Count);
|
||||
(ids[i], ids[rand]) = (ids[rand], ids[i]);
|
||||
}
|
||||
|
||||
validShuffle = true;
|
||||
|
||||
for (var i = 0; i < _slots.Count; i++)
|
||||
{
|
||||
if (_slots[i].RequiredPieceID == ids[i])
|
||||
{
|
||||
validShuffle = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _slots.Count; i++)
|
||||
await _slots[i].SetPiece(ids[i], false);
|
||||
}
|
||||
|
||||
private void EvaluateLocks()
|
||||
{
|
||||
foreach (var slot in _slots)
|
||||
{
|
||||
if (slot.IsCorrect())
|
||||
slot.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckCompleted()
|
||||
{
|
||||
return _slots.All(slot => slot.IsCorrect());
|
||||
}
|
||||
|
||||
private void MarkAsCompleted(bool lockSlots = true)
|
||||
{
|
||||
if (IsCompleted)
|
||||
return;
|
||||
|
||||
IsCompleted = true;
|
||||
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopDownstairsDoorOpen, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopPuzzleBox, true, false);
|
||||
|
||||
if (lockSlots && _lockSlotsOnComplete)
|
||||
{
|
||||
foreach (var slot in _slots)
|
||||
slot.Lock();
|
||||
}
|
||||
|
||||
UpdateInteractionLayers();
|
||||
}
|
||||
|
||||
public override UniTask CompletePuzzle()
|
||||
{
|
||||
if (_sideEffectsFired)
|
||||
return UniTask.CompletedTask;
|
||||
|
||||
_sideEffectsFired = true;
|
||||
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopDownstairsDoorOpen, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopPuzzleBox, true);
|
||||
|
||||
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.WorkshopPuzzleBoxUnlocked));
|
||||
EventCoordinator.Publish(new UnlockAchievementEvent(AchievementID.WorkshopPuzzleBoxSolved));
|
||||
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.DoorCreek));
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5635d368ea404b5183611926e6a6cfcb
|
||||
timeCreated: 1773330168
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81caa7ed407f4737aae0a52a6b2f60f2
|
||||
timeCreated: 1773330146
|
||||
@@ -0,0 +1,281 @@
|
||||
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.Progression;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Managers.Hints.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Services.Puzzles.Base;
|
||||
using BriarQueen.Game.Items.Pickups.ChapterOne.Workshop;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using MemoryPack;
|
||||
using UnityEngine;
|
||||
using Sprite = UnityEngine.Sprite;
|
||||
|
||||
namespace BriarQueen.Game.Puzzles.ChapterOne.Workshop.CandlePuzzle
|
||||
{
|
||||
[Serializable]
|
||||
[MemoryPackable]
|
||||
public partial struct CandlePuzzleState
|
||||
{
|
||||
public bool IsCompleted;
|
||||
public string[] PlacedCandleIds;
|
||||
public bool[] LockedSlots;
|
||||
}
|
||||
|
||||
public class CandleBasePuzzle : BasePuzzle, IPuzzleStateful, IPuzzleWorldStateSync
|
||||
{
|
||||
[Header("Sprites")]
|
||||
[SerializedDictionary("Candle Colour", "Sprite")]
|
||||
[SerializeField]
|
||||
private SerializedDictionary<Candle.CandleColour, Sprite> _candleSprites = new();
|
||||
|
||||
[Header("Slots")]
|
||||
[SerializeField]
|
||||
private List<CandleSlot> _candleSlots = new();
|
||||
|
||||
[SerializeField]
|
||||
private bool _lockSlotsOnComplete = true;
|
||||
|
||||
public override string LevelName => "Candle Holder";
|
||||
public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.WorkshopCandlePuzzle];
|
||||
|
||||
public override Dictionary<int, BaseHint> Hints { get; } = new();
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public UniTask<byte[]> CaptureState()
|
||||
{
|
||||
var count = _candleSlots != null ? _candleSlots.Count : 0;
|
||||
|
||||
var placed = new string[count];
|
||||
var locked = new bool[count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var slot = _candleSlots[i];
|
||||
if (slot == null)
|
||||
{
|
||||
placed[i] = null;
|
||||
locked[i] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
placed[i] = slot.CurrentCandleID;
|
||||
locked[i] = slot.IsLocked;
|
||||
}
|
||||
|
||||
var payload = new CandlePuzzleState
|
||||
{
|
||||
IsCompleted = IsCompleted,
|
||||
PlacedCandleIds = placed,
|
||||
LockedSlots = locked
|
||||
};
|
||||
|
||||
return UniTask.FromResult(MemoryPackSerializer.Serialize(payload));
|
||||
}
|
||||
|
||||
public async UniTask RestoreState(byte[] state)
|
||||
{
|
||||
Debug.Log($"[CandlePuzzleBase] Restoring state: {state}");
|
||||
|
||||
if (state == null || state.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[CandlePuzzleBase] State is null or empty");
|
||||
await RestoreFromWorldVariables();
|
||||
return;
|
||||
}
|
||||
|
||||
CandlePuzzleState payload;
|
||||
|
||||
try
|
||||
{
|
||||
payload = MemoryPackSerializer.Deserialize<CandlePuzzleState>(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[CandlePuzzleBase] Failed to deserialize state: {ex}");
|
||||
await RestoreFromWorldVariables();
|
||||
return;
|
||||
}
|
||||
|
||||
await RestoreSlotsAsync(payload);
|
||||
}
|
||||
|
||||
public void SyncWorldStateToSave()
|
||||
{
|
||||
SyncWorldStateFromSlots();
|
||||
}
|
||||
|
||||
public async UniTask NotifySlotChanged()
|
||||
{
|
||||
SyncWorldStateFromSlots();
|
||||
await CheckPuzzleCompleted();
|
||||
EventCoordinator.Publish(new RequestGameSaveEvent());
|
||||
}
|
||||
|
||||
public async UniTask CheckPuzzleCompleted()
|
||||
{
|
||||
if (_candleSlots == null || _candleSlots.Count == 0)
|
||||
{
|
||||
IsCompleted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var allCorrect = _candleSlots.Where(s => s != null).All(s => s.CorrectlySet);
|
||||
|
||||
if (!IsCompleted && allCorrect)
|
||||
{
|
||||
IsCompleted = true;
|
||||
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopSafeUnlocked, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopCandlePuzzle, true, false);
|
||||
|
||||
if (_lockSlotsOnComplete)
|
||||
{
|
||||
foreach (var slot in _candleSlots)
|
||||
{
|
||||
if (slot != null)
|
||||
slot.SetLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
await CompletePuzzle();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = allCorrect;
|
||||
}
|
||||
}
|
||||
|
||||
public override UniTask CompletePuzzle()
|
||||
{
|
||||
Debug.Log("[CandlePuzzleBase] Complete puzzle");
|
||||
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopSafeUnlocked, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopCandlePuzzle, true);
|
||||
|
||||
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.WorkshopSafeUnlocked));
|
||||
EventCoordinator.Publish(new UnlockAchievementEvent(AchievementID.WorkshopSafeUnlocked));
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
private void SyncWorldStateFromSlots()
|
||||
{
|
||||
var save = SaveManager.CurrentSave;
|
||||
|
||||
if (save == null || save.PersistentVariables == null)
|
||||
return;
|
||||
|
||||
save.PersistentVariables.Game.WorkshopCandleSlotsFilled ??= new Dictionary<int, string>();
|
||||
var map = save.PersistentVariables.Game.WorkshopCandleSlotsFilled;
|
||||
|
||||
map.Clear();
|
||||
|
||||
foreach (var slot in _candleSlots)
|
||||
{
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
var candleId = slot.CurrentCandleID;
|
||||
if (!string.IsNullOrEmpty(candleId))
|
||||
map[slot.SlotID] = candleId;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask RestoreSlotsAsync(CandlePuzzleState payload)
|
||||
{
|
||||
var count = _candleSlots != null ? _candleSlots.Count : 0;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var slot = _candleSlots[i];
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
var candleId = payload.PlacedCandleIds != null && i < payload.PlacedCandleIds.Length
|
||||
? payload.PlacedCandleIds[i]
|
||||
: null;
|
||||
|
||||
var locked = payload.LockedSlots != null && i < payload.LockedSlots.Length && payload.LockedSlots[i];
|
||||
|
||||
ItemDataSo candleSo = null;
|
||||
if (!string.IsNullOrEmpty(candleId))
|
||||
candleSo = ItemRegistry.FindItemTemplateByID(candleId);
|
||||
|
||||
await slot.RestorePlacedCandle(candleSo, locked);
|
||||
}
|
||||
|
||||
IsCompleted = payload.IsCompleted;
|
||||
|
||||
if (IsCompleted)
|
||||
{
|
||||
SaveManager.SetLevelFlag(LevelFlag.WorkshopSafeUnlocked, true, false);
|
||||
SaveManager.SetPuzzleCompleted(PuzzleKey.WorkshopCandlePuzzle, true, false);
|
||||
}
|
||||
|
||||
if (IsCompleted && _lockSlotsOnComplete)
|
||||
{
|
||||
foreach (var slot in _candleSlots)
|
||||
{
|
||||
if (slot != null)
|
||||
slot.SetLocked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask RestoreFromWorldVariables()
|
||||
{
|
||||
Debug.Log("[CandlePuzzleBase] RestoreFromWorldVariables()");
|
||||
|
||||
var save = SaveManager.CurrentSave;
|
||||
if (save == null || save.PersistentVariables == null)
|
||||
return;
|
||||
|
||||
var map = save.PersistentVariables.Game.WorkshopCandleSlotsFilled;
|
||||
if (map == null || map.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[CandlePuzzleBase] WorkshopCandleSlotsFilled is empty");
|
||||
IsCompleted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kvp in map)
|
||||
Debug.Log($"[CandlePuzzleBase] World slot {kvp.Key} -> {kvp.Value}");
|
||||
|
||||
foreach (var slot in _candleSlots)
|
||||
{
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
if (!map.TryGetValue(slot.SlotID, out var candleId) || string.IsNullOrEmpty(candleId))
|
||||
{
|
||||
await slot.RestorePlacedCandle(null, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Log($"Looking up candle template for ID: {candleId}");
|
||||
|
||||
var candleSo = ItemRegistry.FindItemTemplateByID(candleId);
|
||||
if (candleSo == null)
|
||||
Debug.LogError($"Candle template NOT FOUND for ID: {candleId}");
|
||||
|
||||
if (candleSo == null)
|
||||
Debug.LogWarning($"[CandlePuzzleBase] Could not find candle template for ID '{candleId}'");
|
||||
|
||||
await slot.RestorePlacedCandle(candleSo, false);
|
||||
}
|
||||
|
||||
await CheckPuzzleCompleted();
|
||||
}
|
||||
|
||||
internal Sprite GetCandleSprite(Candle.CandleColour colour)
|
||||
{
|
||||
return _candleSprites != null && _candleSprites.TryGetValue(colour, out var sprite) ? sprite : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74e3381d84864aafb2c4c1c4a90a84f4
|
||||
timeCreated: 1772735921
|
||||
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using BriarQueen.Game.Items.Pickups.ChapterOne.Workshop;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BriarQueen.Game.Puzzles.ChapterOne.Workshop.CandlePuzzle
|
||||
{
|
||||
public class CandleSlot : BaseItem
|
||||
{
|
||||
private static readonly HashSet<string> CandleIDs = new()
|
||||
{
|
||||
ItemIDs.Pickups[ItemKey.RedCandle],
|
||||
ItemIDs.Pickups[ItemKey.OrangeCandle],
|
||||
ItemIDs.Pickups[ItemKey.YellowCandle],
|
||||
ItemIDs.Pickups[ItemKey.GreenCandle],
|
||||
ItemIDs.Pickups[ItemKey.BlueCandle],
|
||||
ItemIDs.Pickups[ItemKey.IndigoCandle],
|
||||
ItemIDs.Pickups[ItemKey.VioletCandle]
|
||||
};
|
||||
|
||||
private static readonly Dictionary<ItemKey, Candle.CandleColour> CandleKeyToColour = new()
|
||||
{
|
||||
{ ItemKey.RedCandle, Candle.CandleColour.Red },
|
||||
{ ItemKey.OrangeCandle, Candle.CandleColour.Orange },
|
||||
{ ItemKey.YellowCandle, Candle.CandleColour.Yellow },
|
||||
{ ItemKey.GreenCandle, Candle.CandleColour.Green },
|
||||
{ ItemKey.BlueCandle, Candle.CandleColour.Blue },
|
||||
{ ItemKey.IndigoCandle, Candle.CandleColour.Indigo },
|
||||
{ ItemKey.VioletCandle, Candle.CandleColour.Violet }
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
private CandleBasePuzzle _basePuzzleOwner;
|
||||
|
||||
[Header("Visuals")]
|
||||
[SerializeField]
|
||||
private Image _candleImage;
|
||||
|
||||
[Header("Rules")]
|
||||
[SerializeField]
|
||||
private ItemKey _requiredCandleID;
|
||||
|
||||
[SerializeField]
|
||||
private int _slotID;
|
||||
|
||||
[SerializeField]
|
||||
private bool _preventRemovingCorrectCandle = true;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
[SerializeField]
|
||||
private ItemDataSo _filledCandle;
|
||||
|
||||
private Sequence _sequence;
|
||||
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
|
||||
public override string InteractableName => IsEmpty ? "Empty Holder" : _filledCandle.ItemName;
|
||||
private bool IsEmpty => _filledCandle == null;
|
||||
|
||||
public int SlotID => _slotID;
|
||||
public string CurrentCandleID => _filledCandle != null ? _filledCandle.UniqueID : null;
|
||||
public bool CorrectlySet => !IsEmpty && _filledCandle.ItemKey == _requiredCandleID;
|
||||
public bool IsLocked { get; private set; }
|
||||
|
||||
public void SetLocked(bool locked)
|
||||
{
|
||||
IsLocked = locked;
|
||||
}
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (IsLocked)
|
||||
return;
|
||||
|
||||
if (_preventRemovingCorrectCandle && CorrectlySet)
|
||||
return;
|
||||
|
||||
if (!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.FindCandle)));
|
||||
return;
|
||||
}
|
||||
|
||||
await PickupCandle();
|
||||
await _basePuzzleOwner.NotifySlotChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsCandle(item))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.DoesntBelong)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsEmpty)
|
||||
await PickupCandle();
|
||||
|
||||
await PlaceCandle(item);
|
||||
await _basePuzzleOwner.NotifySlotChanged();
|
||||
}
|
||||
|
||||
public async UniTask RestorePlacedCandle(ItemDataSo candleSo, bool locked)
|
||||
{
|
||||
IsLocked = locked;
|
||||
|
||||
EnsureCanvasGroup();
|
||||
CancelTweenIfRunning();
|
||||
|
||||
_filledCandle = candleSo;
|
||||
|
||||
if (_candleImage != null)
|
||||
_candleImage.sprite = _filledCandle != null ? GetSpriteForCandle(_filledCandle) : null;
|
||||
|
||||
_canvasGroup.alpha = _filledCandle != null ? 1f : 0f;
|
||||
|
||||
await UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
private async UniTask PlaceCandle(ItemDataSo candleSo)
|
||||
{
|
||||
if (candleSo == null) return;
|
||||
|
||||
EnsureCanvasGroup();
|
||||
CancelTweenIfRunning();
|
||||
|
||||
_filledCandle = candleSo;
|
||||
|
||||
if (_candleImage != null)
|
||||
_candleImage.sprite = GetSpriteForCandle(_filledCandle);
|
||||
|
||||
_canvasGroup.alpha = 0f;
|
||||
|
||||
_sequence = Sequence.Create().Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
||||
{
|
||||
startValue = _canvasGroup.alpha,
|
||||
endValue = 1f,
|
||||
settings = new TweenSettings { duration = 0.3f }
|
||||
}));
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
await _sequence.ToUniTask(cancellationToken: _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCts();
|
||||
}
|
||||
|
||||
PlayerManager.RemoveItem(candleSo);
|
||||
}
|
||||
|
||||
private async UniTask PickupCandle()
|
||||
{
|
||||
if (IsEmpty) return;
|
||||
|
||||
EnsureCanvasGroup();
|
||||
CancelTweenIfRunning();
|
||||
|
||||
_sequence = Sequence.Create().Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
||||
{
|
||||
startValue = _canvasGroup.alpha,
|
||||
endValue = 0f,
|
||||
settings = new TweenSettings { duration = 0.3f }
|
||||
}));
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
await _sequence.ToUniTask(cancellationToken: _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCts();
|
||||
}
|
||||
|
||||
PlayerManager.CollectItem(_filledCandle);
|
||||
|
||||
_filledCandle = null;
|
||||
|
||||
if (_candleImage != null)
|
||||
_candleImage.sprite = null;
|
||||
}
|
||||
|
||||
private void EnsureCanvasGroup()
|
||||
{
|
||||
if (_canvasGroup == null)
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
private void CancelTweenIfRunning()
|
||||
{
|
||||
if (_sequence.isAlive)
|
||||
_sequence.Complete();
|
||||
|
||||
DisposeCts();
|
||||
}
|
||||
|
||||
private void DisposeCts()
|
||||
{
|
||||
if (_cancellationTokenSource == null) return;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private Sprite GetSpriteForCandle(ItemDataSo candle)
|
||||
{
|
||||
if (candle == null || _basePuzzleOwner == null)
|
||||
{
|
||||
Debug.Log($"[Candle Puzzle] Candle is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (CandleKeyToColour.TryGetValue(candle.ItemKey, out var colour))
|
||||
{
|
||||
Debug.Log($"[Candle Puzzle] {candle.ItemKey} : {colour}");
|
||||
return _basePuzzleOwner.GetCandleSprite(colour);
|
||||
}
|
||||
|
||||
// fallback if candle ID not found
|
||||
return _basePuzzleOwner.GetCandleSprite(Candle.CandleColour.Red);
|
||||
}
|
||||
|
||||
private bool IsCandle(ItemDataSo itemSo)
|
||||
{
|
||||
return itemSo != null && CandleIDs.Contains(itemSo.UniqueID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cdb672cabe34a5c820eef6dfebfef72
|
||||
timeCreated: 1772736131
|
||||
Reference in New Issue
Block a user