Laxley Grandfather Clock puzzle artwork done.

This commit is contained in:
2026-03-28 18:53:38 +00:00
parent 69306a141b
commit 83e9a35d2f
152 changed files with 12822 additions and 194 deletions

View File

@@ -1,12 +1,15 @@
using System;
[AttributeUsage(AttributeTargets.Field)]
public abstract class DisplayNameAttribute : Attribute
namespace BriarQueen.Data.Attributes
{
public string Name { get; }
public DisplayNameAttribute(string name)
[AttributeUsage(AttributeTargets.Field)]
public class DisplayNameAttribute : Attribute
{
Name = name;
public string Name { get; }
public DisplayNameAttribute(string name)
{
Name = name;
}
}
}

View File

@@ -108,6 +108,9 @@ namespace BriarQueen.Data.IO.Saves
VillageStreetVinesCut,
LaxleyFireplaceExtinguished,
LaxleyLockboxOpened,
LaxleyClockSolved,
LaxleyHourHandRetrieved,
LaxleyMinuteHandRetrieved,
}
[Serializable]

View File

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

View File

@@ -53,7 +53,9 @@ namespace BriarQueen.Data.Identifiers
ChapterOneVillageMarketSquareStatue,
ChapterOneVillageMarketSquareFirepit,
ChapterOneVillageEnd,
ChapterOneVillageEndChurch
ChapterOneVillageEndChurch,
ChapterOneLaxleyHouseLockbox,
ChapterOneLaxleyHouseUpstairsStudy,
}
public enum AssetItemKey

View File

@@ -21,6 +21,7 @@ namespace BriarQueen.Data.Identifiers
SharpenKnife,
LockBoxNumberReel,
LockboxOpening,
ClockOpening,
}
public enum UIFXKey
@@ -63,6 +64,8 @@ namespace BriarQueen.Data.Identifiers
{ SFXKey.ResetPuzzle, "SFX_ResetPuzzle" },
{ SFXKey.SharpenKnife, "SFX_SharpenKnife"},
{ SFXKey.LockBoxNumberReel, "SFX_LockBoxNumberReel" },
{ SFXKey.LockboxOpening, "SFX_LockboxOpening" },
{ SFXKey.ClockOpening, "SFX_ClockOpening" },
});
public static readonly IReadOnlyDictionary<UIFXKey, string> UIFX =

View File

@@ -8,6 +8,7 @@ namespace BriarQueen.Data.Identifiers
None = 0,
WorkshopDiary = 1,
LaxleyHouseBillOfSale = 2,
GranddfatherClockPlaque = 3
}
public enum ClueEntryID
@@ -25,20 +26,15 @@ namespace BriarQueen.Data.Identifiers
WorkshopFadedPhoto = 1
}
public enum LocationEntryID
{
None = 0,
Village = 1,
Workshop = 2
}
public static class CodexEntryIDs
{
public static readonly IReadOnlyDictionary<BookEntryID, string> Books =
new ReadOnlyDictionary<BookEntryID, string>(
new Dictionary<BookEntryID, string>
{
{ BookEntryID.WorkshopDiary, "BOOK_WorkshopDiary" }
{ BookEntryID.WorkshopDiary, "BOOK_WorkshopDiary" },
{ BookEntryID.LaxleyHouseBillOfSale, "BOOK_LaxleyHouseBillOfSale" },
{ BookEntryID.GranddfatherClockPlaque, "BOOK_GranddfatherClockPlaque" },
});
public static readonly IReadOnlyDictionary<ClueEntryID, string> Clues =

View File

@@ -15,7 +15,7 @@ namespace BriarQueen.Data.Identifiers
RagFallsApart = 7,
LooksImportant = 8,
WrongTool = 9,
RefillBucket = 10,
CollectEndlessGoblets = 10,
}
public enum LevelInteractKey
@@ -50,7 +50,11 @@ namespace BriarQueen.Data.Identifiers
PumpTurnOn = 16,
FireHot = 17,
ExtinguishFire = 18,
CauldronBoiledAway = 19
CauldronBoiledAway = 19,
LaxleyHouseBrokenClock = 20,
LaxleyGrandfatherClockMissingBothHands = 21,
LaxleyGrandfatherClockMissingHourHand = 22,
LaxleyGrandfatherClockMissingMinuteHand = 23,
}
public enum UIInteractKey
@@ -58,66 +62,72 @@ namespace BriarQueen.Data.Identifiers
None = 0,
EmptySlot = 1,
}
public static class InteractEventIDs
{
public static readonly IReadOnlyDictionary<ItemInteractKey, string> ItemInteractions =
new ReadOnlyDictionary<ItemInteractKey, string>(
new Dictionary<ItemInteractKey, string>
{
{ ItemInteractKey.EmptyHands, "I need to put my tools away." },
{ ItemInteractKey.CantUseItem, "That won't work here." },
{ ItemInteractKey.RustyKnife, "It's too blunt to be useful." },
{ ItemInteractKey.SomethingMissing, "Something's missing." },
{ ItemInteractKey.PliersSnapped, "The pliers snapped. They're no use now." },
{ 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.RefillBucket, "I need to refill this before I can use."}
{ ItemInteractKey.EmptyHands, "My hands are too full for that." },
{ ItemInteractKey.CantUseItem, "That wont work here." },
{ ItemInteractKey.RustyKnife, "Too dull to be of any use." },
{ ItemInteractKey.SomethingMissing, "Something isnt right." },
{ ItemInteractKey.PliersSnapped, "The pliers snap. Thats the end of them." },
{ ItemInteractKey.CarefulInteract, "I should take care with this." },
{ ItemInteractKey.RagFallsApart, "It fell apart in my hands." },
{ ItemInteractKey.LooksImportant, "This feels important." },
{ ItemInteractKey.WrongTool, "This isnt the right tool." },
{ ItemInteractKey.CollectEndlessGoblets, "Faint symbols coil across the goblets surface." },
});
public static readonly IReadOnlyDictionary<LevelInteractKey, string> LevelInteractions =
new ReadOnlyDictionary<LevelInteractKey, string>(
new Dictionary<LevelInteractKey, string>
{
{ LevelInteractKey.WaterValve, "I've already turned the water on." },
{ LevelInteractKey.ClearVinesOutside, "I need to clear the vines outside first." },
{ LevelInteractKey.PumphouseChain, "There must be a key around here somewhere." },
{ LevelInteractKey.CutVines, "I need something to cut through these." },
{ LevelInteractKey.WorkshopLockedSafe, "It's locked tight." },
{ LevelInteractKey.UnlockedPumphouse, "You used the Pumphouse Key."},
{ LevelInteractKey.WaterValve, "The water is already flowing." },
{ LevelInteractKey.ClearVinesOutside, "The vines still block the way outside." },
{ LevelInteractKey.PumphouseChain, "Its locked by something more than rust." },
{ LevelInteractKey.CutVines, "These wont give way by hand." },
{ LevelInteractKey.WorkshopLockedSafe, "Sealed tight." },
{ LevelInteractKey.UnlockedPumphouse, "The lock gives with a dull click." },
});
public static readonly IReadOnlyDictionary<EnvironmentInteractKey, string> EnvironmentInteractions =
new ReadOnlyDictionary<EnvironmentInteractKey, string>(
new Dictionary<EnvironmentInteractKey, string>
{
{ EnvironmentInteractKey.BrokenLantern, "It's too broken to use." },
{ EnvironmentInteractKey.WorkshopWriting, "It could be worse... it could be blood." },
{ EnvironmentInteractKey.UseGrindstone, "I could sharpen something on this." },
{ EnvironmentInteractKey.WorkshopBookDisintegrating, "It fell apart in my hands." },
{ EnvironmentInteractKey.UsingKnife, "I should be careful cutting these." },
{ EnvironmentInteractKey.AlreadySharpened, "That should be sharp enough now." },
{ EnvironmentInteractKey.Locked, "It's locked." },
{ EnvironmentInteractKey.CantGoThere, "I can't go that way." },
{ EnvironmentInteractKey.DirtyWindow, "I can't see through all this grime." },
{ EnvironmentInteractKey.WorkshopBagNoItems, "There's nothing left inside." },
{ EnvironmentInteractKey.FindCandle, "I should look for the candle." },
{ EnvironmentInteractKey.DoesntBelong, "That doesn't belong here." },
{ 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.FireHot, "I should put the fire out first."},
{ EnvironmentInteractKey.CauldronBoiledAway, "Whatever was in the cauldron, boiled away long ago."}
{ EnvironmentInteractKey.BrokenLantern, "Beyond repair." },
{ EnvironmentInteractKey.WorkshopWriting, "At least it isnt blood." },
{ EnvironmentInteractKey.UseGrindstone, "This could still sharpen an edge." },
{ EnvironmentInteractKey.WorkshopBookDisintegrating, "It crumbles at a touch." },
{ EnvironmentInteractKey.UsingKnife, "Careful… one slip." },
{ EnvironmentInteractKey.AlreadySharpened, "That edge will do." },
{ EnvironmentInteractKey.Locked, "Locked." },
{ EnvironmentInteractKey.CantGoThere, "No way through." },
{ EnvironmentInteractKey.DirtyWindow, "Nothing visible through the grime." },
{ EnvironmentInteractKey.WorkshopBagNoItems, "Picked clean." },
{ EnvironmentInteractKey.FindCandle, "I need the candle that goes here." },
{ EnvironmentInteractKey.DoesntBelong, "This feels out of place." },
{ EnvironmentInteractKey.SharpGlass, "Still sharp enough to bite." },
{ EnvironmentInteractKey.FreshAndCoolWater, "Cool. Clean. Unexpected." },
{ EnvironmentInteractKey.WorkshopBooks, "Time hasnt been kind to these." },
{ EnvironmentInteractKey.PumpTurnOn, "The pumps shudder back to life." },
{ EnvironmentInteractKey.FireHot, "Too hot to get close." },
{ EnvironmentInteractKey.CauldronBoiledAway, "Whatever it held is long gone." },
{ EnvironmentInteractKey.ExtinguishFire, "The symbols begin to glow as the goblet fills." },
{ EnvironmentInteractKey.LaxleyHouseBrokenClock, "The clock stopped at three-thirty-three." },
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingBothHands, "It's missing both hands. "},
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingHourHand, "The hour hand is missing."},
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingMinuteHand, "The minute hand is missing."},
});
public static readonly IReadOnlyDictionary<UIInteractKey, string> UIInteractions =
new ReadOnlyDictionary<UIInteractKey, string>(
new Dictionary<UIInteractKey, string>
{
{ UIInteractKey.EmptySlot, "Empty slot." },
{ UIInteractKey.EmptySlot, "Empty." },
});
public static string Get(ItemInteractKey key)

View File

@@ -37,7 +37,10 @@ namespace BriarQueen.Data.Identifiers
TornPage4 = 29,
TornPage5 = 30,
IncompleteBook = 31,
CompleteBook = 32
CompleteBook = 32,
Stamp = 33,
LaxleyClockHourHand = 34,
LaxleyClockMinuteHand = 35,
}
public enum EnvironmentKey
@@ -56,6 +59,7 @@ namespace BriarQueen.Data.Identifiers
WorkshopBrokenLantern = 11,
WorkshopWriting = 12,
StreetVines = 13,
GrandfatherClockPlaque = 14,
}
public enum PuzzleSlotKey
@@ -64,7 +68,9 @@ namespace BriarQueen.Data.Identifiers
WorkshopCandleSlot = 1,
WorkshopPuzzleBoxSlot = 2,
FountainGemSlot = 3,
FireplaceLockboxSlot = 4,
GrandfatherClockFace = 5,
GrandfatherClockHand = 6,
}
public static class ItemIDs
@@ -74,7 +80,11 @@ namespace BriarQueen.Data.Identifiers
new Dictionary<PuzzleSlotKey, string>
{
{ PuzzleSlotKey.WorkshopCandleSlot, "PUZ_WorkshopCandleSlot" },
{ PuzzleSlotKey.WorkshopPuzzleBoxSlot, "PUZ_WorkshopPuzzleBoxSlot" }
{ PuzzleSlotKey.WorkshopPuzzleBoxSlot, "PUZ_WorkshopPuzzleBoxSlot" },
{ PuzzleSlotKey.FountainGemSlot, "PUZ_FountainGemSlot" },
{ PuzzleSlotKey.FireplaceLockboxSlot, "PUZ_FireplaceLockboxSlot" },
{ PuzzleSlotKey.GrandfatherClockFace, "PUZ_GrandfatherClockFace" },
{ PuzzleSlotKey.GrandfatherClockHand, "PUZ_GrandfatherClockHand" },
});
public static readonly IReadOnlyDictionary<EnvironmentKey, string> Environment =
@@ -94,37 +104,48 @@ namespace BriarQueen.Data.Identifiers
{ EnvironmentKey.WorkshopBrokenLantern, "ENV_WorkshopBrokenLantern" },
{ EnvironmentKey.WorkshopWriting, "ENV_WorkshopWriting" },
{ EnvironmentKey.StreetVines, "ENV_StreetVines" },
{ EnvironmentKey.GrandfatherClockPlaque, "ENV_GrandfatherClockPlaque" },
});
public static readonly IReadOnlyDictionary<ItemKey, string> Pickups =
new ReadOnlyDictionary<ItemKey, string>(
new Dictionary<ItemKey, string>
{
{ ItemKey.RustedKnife, "00_RustedKnife" },
{ ItemKey.SharpenedKnife, "01_SharpenedKnife" },
{ ItemKey.EmeraldAmulet, "02_EmeraldAmulet" },
{ ItemKey.DustyMirror, "03_DustyMirror" },
{ ItemKey.SmallRag, "04_SmallRag" },
{ ItemKey.GreenCandle, "05_GreenCandle" },
{ ItemKey.IndigoCandle, "06_IndigoCandle" },
{ ItemKey.DirtyMagnifyingGlass, "07_DirtyMagnifyingGlass" },
{ ItemKey.PumphouseKey, "08_PumphouseKey" },
{ ItemKey.RedCandle, "09_RedCandle" },
{ ItemKey.OrangeCandle, "10_OrangeCandle" },
{ ItemKey.YellowCandle, "11_YellowCandle" },
{ ItemKey.BlueCandle, "12_BlueCandle" },
{ ItemKey.VioletCandle, "13_VioletCandle" },
{ ItemKey.Pliers, "14_Pliers" },
{ ItemKey.Emerald, "15_Emerald" },
{ ItemKey.Sapphire, "16_Sapphire" },
{ ItemKey.Ruby, "17_Ruby" },
{ ItemKey.RubyRing, "18_RubyRing" },
{ ItemKey.SilverCoin, "19_SilverCoin" },
{ ItemKey.GoldCoin, "20_GoldCoin" },
{ ItemKey.GrindstoneAxlePin, "21_GrindstoneAxlePin" },
{ ItemKey.Diamond, "22_Diamond" },
{ ItemKey.DiamondTiara, "23_DiamondTiara" },
{ ItemKey.DustySapphire, "24_DustySapphire" },
{ ItemKey.RustedKnife, "01_RustedKnife" },
{ ItemKey.SharpenedKnife, "02_SharpenedKnife" },
{ ItemKey.EmeraldAmulet, "03_EmeraldAmulet" },
{ ItemKey.DustyMirror, "04_DustyMirror" },
{ ItemKey.SmallRag, "05_SmallRag" },
{ ItemKey.GreenCandle, "06_GreenCandle" },
{ ItemKey.IndigoCandle, "07_IndigoCandle" },
{ ItemKey.DirtyMagnifyingGlass, "08_DirtyMagnifyingGlass" },
{ ItemKey.PumphouseKey, "09_PumphouseKey" },
{ ItemKey.RedCandle, "10_RedCandle" },
{ ItemKey.OrangeCandle, "11_OrangeCandle" },
{ ItemKey.YellowCandle, "12_YellowCandle" },
{ ItemKey.BlueCandle, "13_BlueCandle" },
{ ItemKey.VioletCandle, "14_VioletCandle" },
{ ItemKey.Pliers, "15_Pliers" },
{ ItemKey.Emerald, "16_Emerald" },
{ ItemKey.Sapphire, "17_Sapphire" },
{ ItemKey.Ruby, "18_Ruby" },
{ ItemKey.RubyRing, "19_RubyRing" },
{ ItemKey.SilverCoin, "20_SilverCoin" },
{ ItemKey.GoldCoin, "21_GoldCoin" },
{ ItemKey.GrindstoneAxlePin, "22_GrindstoneAxlePin" },
{ ItemKey.Diamond, "23_Diamond" },
{ ItemKey.DiamondTiara, "24_DiamondTiara" },
{ ItemKey.DustySapphire, "25_DustySapphire" },
{ ItemKey.TornPage1, "26_TornPage1" },
{ ItemKey.TornPage2, "27_TornPage2" },
{ ItemKey.TornPage3, "28_TornPage3" },
{ ItemKey.TornPage4, "29_TornPage4" },
{ ItemKey.TornPage5, "30_TornPage5" },
{ ItemKey.IncompleteBook, "31_IncompleteBook" },
{ ItemKey.CompleteBook, "32_CompleteBook" },
{ ItemKey.Stamp, "33_Stamp" },
{ ItemKey.LaxleyClockHourHand, "34_LaxleyClockHourHand" },
{ ItemKey.LaxleyClockMinuteHand, "35_LaxleyClockMinuteHand" },
});
public static string Get(ItemKey key)

View File

@@ -6,5 +6,6 @@ namespace BriarQueen.Data.Identifiers
Village = 1,
Workshop = 2,
LaxleyHouse = 3,
Pumphouse = 4,
}
}

View File

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

View File

@@ -1,10 +1,17 @@
using BriarQueen.Data.Attributes;
namespace BriarQueen.Data.Identifiers
{
public enum ToolID
{
[DisplayName("Empty Hands")]
None = 0,
[DisplayName("Sharpened Knife")]
Knife = 1,
WaterBucket,
[DisplayName("Water Goblet")]
EndlessGoblet,
}
}

View File

@@ -25,15 +25,15 @@ namespace BriarQueen.Data.Identifiers
{
{
TutorialPopupID.ReturnToPreviousLevel,
"Click the bottom corners to go back the way you came."
"Click the bottom corners to return to the previous area."
},
{
TutorialPopupID.UsingItemsTogether,
"Select an item, then click another to use it on that object."
"Select one item, then click another to use them together."
},
{
TutorialPopupID.HideHUD,
"Press 'H' to hide the HUD and see the world more clearly."
"Press 'H' to hide the HUD."
},
{
TutorialPopupID.ExitItems,
@@ -41,31 +41,31 @@ namespace BriarQueen.Data.Identifiers
},
{
TutorialPopupID.MultipleUseItems,
"Some items can be used more than once, but will eventually wear out."
"Some items can be used multiple times, but they may wear out."
},
{
TutorialPopupID.DarkRooms,
"Dark rooms hide what matters. Look carefully—light reveals what they conceal."
"Dark rooms can hide important details. Use light to reveal them."
},
{
TutorialPopupID.Codex,
"New discoveries are added to your codex. Press 'C' to review what you've gathered."
"The Codex stores information you've discovered. Press 'C' to open it."
},
{
TutorialPopupID.HiddenItems,
"Some things are hidden on purpose. Search carefully to uncover them."
"Some items are hidden. Search carefully."
},
{
TutorialPopupID.ResetPuzzles,
"Some puzzles can be reset if you make a mistake."
"Some puzzles can be reset."
},
{
TutorialPopupID.Tools,
"You'll find tools as you explore. Each has its own purpose—try them on different objects. Press 'Y' to view your tools."
"You'll find tools as you explore. Try them on different objects. Press 'Y' to view your tools."
},
{
TutorialPopupID.ItemsAway,
"Right-click to put away any items."
"Right-click to put away the current item."
},
{
TutorialPopupID.ItemCycling,

View File

@@ -2,5 +2,5 @@ using BriarQueen.Framework.Events.System;
namespace BriarQueen.Framework.Events.Progression
{
public record RequestHintEvent : IEvent;
public abstract record RequestHintEvent : IEvent;
}

View File

@@ -11,5 +11,5 @@ namespace BriarQueen.Framework.Events.Progression
/// By default stages are monotonic (only increase).
/// Set Force=true to allow decreasing (e.g., reset/override).
/// </summary>
public record UpdateHintProgressEvent(string LevelID, int Stage, bool Force = false) : IEvent;
public abstract record UpdateHintProgressEvent(string LevelID, int Stage, bool Force = false) : IEvent;
}

View File

@@ -1,7 +1,22 @@
using System;
using System.Reflection;
using BriarQueen.Data.Attributes;
namespace BriarQueen.Framework.Extensions
{
public class EnumExtensions
public static class EnumExtensions
{
public static string GetDisplayName(this Enum value)
{
var type = value.GetType();
var field = type.GetField(value.ToString());
if (field == null)
return value.ToString();
var attribute = field.GetCustomAttribute<DisplayNameAttribute>();
return attribute != null ? attribute.Name : value.ToString();
}
}
}

View File

@@ -1,7 +1,99 @@
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace BriarQueen.Framework.Extensions
{
public class StringExtensions
public static class StringExtensions
{
private static readonly string[] Prefixes =
{
"ENV_",
"PUZ_",
"SUB_",
"UI_",
"SFX_",
"BGM_",
"BOOK_",
"CLUE_",
"PHOTO_"
};
private static readonly IReadOnlyDictionary<string, string> Overrides =
new Dictionary<string, string>
{
{ "Pumphouse", "Pump House" },
{ "Lockbox", "Lockbox" },
{ "Codex", "Codex" },
{ "UIFX", "UI FX" },
{ "SFX", "SFX" },
{ "BGM", "BGM" }
};
public static string Prettify(this string value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
value = StripPrefix(value);
value = value.Replace("_", " ");
value = AddSpacing(value);
value = Regex.Replace(value, @"\s+", " ").Trim();
if (value.Length == 0)
return string.Empty;
var words = value.Split(' ');
var sb = new StringBuilder();
for (int i = 0; i < words.Length; i++)
{
var word = words[i];
if (string.IsNullOrWhiteSpace(word))
continue;
string formatted = FormatWord(word);
sb.Append(formatted);
if (i < words.Length - 1)
sb.Append(' ');
}
return sb.ToString();
}
private static string StripPrefix(string value)
{
foreach (var prefix in Prefixes)
{
if (value.StartsWith(prefix))
return value.Substring(prefix.Length);
}
return value;
}
private static string AddSpacing(string value)
{
value = Regex.Replace(value, "([a-z0-9])([A-Z])", "$1 $2");
value = Regex.Replace(value, "([A-Z]+)([A-Z][a-z])", "$1 $2");
value = Regex.Replace(value, "([a-zA-Z])([0-9])", "$1 $2");
value = Regex.Replace(value, "([0-9])([a-zA-Z])", "$1 $2");
return value;
}
private static string FormatWord(string word)
{
if (Overrides.TryGetValue(word, out var overrideValue))
return overrideValue;
if (word.Length > 1 && word.ToUpperInvariant() == word)
return word;
return char.ToUpperInvariant(word[0]) + word.Substring(1).ToLowerInvariant();
}
}
}

View File

@@ -1,6 +1,8 @@
using System;
using BriarQueen.Data.Identifiers;
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.Player;
using NaughtyAttributes;
using UnityEngine;
using VContainer;
@@ -10,15 +12,21 @@ namespace BriarQueen.Framework.Managers
public class DebugManager : MonoBehaviour
{
private SaveManager _saveManager;
private PlayerManager _playerManager;
[Header("Current Loaded Save")]
[SerializeField, ReadOnly]
private SaveGame _currentSave;
[Header("Interactive Debugging")]
[SerializeField]
private ItemKey _itemToGive;
[Inject]
public void Construct(SaveManager saveManager)
public void Construct(SaveManager saveManager, PlayerManager playerManager)
{
_saveManager = saveManager;
_playerManager = playerManager;
}
public void Start()
@@ -30,5 +38,14 @@ namespace BriarQueen.Framework.Managers
{
_currentSave = save;
}
[Button]
private void GiveItem()
{
if (_itemToGive == ItemKey.None)
return;
_playerManager.CollectItem(ItemIDs.Get(_itemToGive));
}
}
}

View File

@@ -9,6 +9,7 @@ using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.Save;
using BriarQueen.Framework.Extensions;
using BriarQueen.Framework.Managers.Player.Data;
using Cysharp.Threading.Tasks;
using MemoryPack;
using UnityEngine;
@@ -418,5 +419,17 @@ namespace BriarQueen.Framework.Managers.IO
return CurrentSave.PersistentVariables.Game.GetLevelFlag(levelFlag);
}
public bool HasCollectedItem(string uniqueIdentifier)
{
if (uniqueIdentifier == null)
return false;
var collected = CurrentSave?.CollectedItems;
if (collected == null)
return false;
return collected.Any(x => x.UniqueIdentifier == uniqueIdentifier);
}
}
}

View File

@@ -14,5 +14,6 @@ namespace BriarQueen.Framework.Managers.Interaction.Data
UniTask EnterHover();
UniTask ExitHover();
}
}

View File

@@ -375,6 +375,7 @@ namespace BriarQueen.Framework.Managers.Interaction
return;
if (_currentHovered != null)
{
try
{
await _currentHovered.ExitHover();
@@ -382,20 +383,19 @@ namespace BriarQueen.Framework.Managers.Interaction
catch
{
}
}
_currentHovered = next;
_eventCoordinator.Publish(
new HoverInteractableChangedEvent(_currentHovered));
_eventCoordinator.Publish(new HoverInteractableChangedEvent(_currentHovered));
var cursor =
_currentHovered?.ApplicableCursorStyle
?? UICursorService.CursorStyle.Default;
var cursor = _currentHovered?.ApplicableCursorStyle
?? UICursorService.CursorStyle.Default;
_eventCoordinator.Publish(
new CursorStyleChangeEvent(cursor));
_eventCoordinator.Publish(new CursorStyleChangeEvent(cursor));
if (_currentHovered != null)
{
try
{
await _currentHovered.EnterHover();
@@ -403,6 +403,7 @@ namespace BriarQueen.Framework.Managers.Interaction
catch
{
}
}
}
private async UniTask ClearHover()
@@ -437,6 +438,7 @@ namespace BriarQueen.Framework.Managers.Interaction
return;
_currentHovered.OnInteract(_selectedItem).Forget();
}
private void OnRightClickReceived(OnRightClickEvent obj)

View File

@@ -70,6 +70,7 @@ namespace BriarQueen.Framework.Managers.Levels.Data
public virtual string InteractableName =>
!string.IsNullOrWhiteSpace(_interactableTooltip) ? _interactableTooltip : _itemData.ItemName;
/// <summary>
/// Called when the item is interacted with. Defaults to Pickup.

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Extensions;
using BriarQueen.Framework.Managers.Interaction;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.UI.Base;
@@ -222,16 +223,15 @@ namespace BriarQueen.Framework.Managers.UI
private string GetToolbeltTextForEntry(ToolID toolID, bool lost)
{
if (lost)
return $"You lost the {toolID.ToString()}.";
return $"You lost the {toolID.GetDisplayName()}.";
else
return $"You gained the {toolID.ToString()}.";
return $"You gained the {toolID.GetDisplayName()}.";
}
private string GetCodexTextForEntry(CodexType codexType)
{
return codexType switch
{
CodexType.BookEntry => "You've acquired a new book entry.",
CodexType.BookEntry => "You've acquired a new document.",
CodexType.PuzzleClue => "You've acquired a new puzzle clue.",
CodexType.Photo => "You've acquired a new photo.",
_ => string.Empty

View File

@@ -46,10 +46,30 @@ namespace BriarQueen.Game.Items.Environment.ChapterOne.VillageStreet
private void PublishFailureMessage()
{
var message = SettingsService.Game.AutoUseTools
? InteractEventIDs.Get(LevelInteractKey.CutVines)
: InteractEventIDs.Get(ItemInteractKey.WrongTool);
var autoUseTools = SettingsService != null &&
SettingsService.Game != null &&
SettingsService.Game.AutoUseTools;
var equippedTool = PlayerManager.GetEquippedTool();
string message;
if (equippedTool == ToolID.None)
{
message = InteractEventIDs.Get(LevelInteractKey.CutVines);
}
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(LevelInteractKey.CutVines);
}
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

@@ -3,6 +3,7 @@ using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using BriarQueen.Framework.Managers.UI;
using Cysharp.Threading.Tasks;
using UnityEngine;
@@ -14,6 +15,10 @@ namespace BriarQueen.Game.Items.Environment.General
[SerializeField]
private LevelFlag _levelFlag;
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (item != null)
@@ -22,7 +27,7 @@ namespace BriarQueen.Game.Items.Environment.General
return;
}
if (!PlayerManager.CanUseTool(ToolID.WaterBucket))
if (!PlayerManager.CanUseTool(ToolID.EndlessGoblet))
{
PublishFailureMessage();
return;

View File

@@ -52,10 +52,30 @@ namespace BriarQueen.Game.Items.Environment.General
private void PublishFailureMessage()
{
var message = SettingsService.Game.AutoUseTools
? InteractEventIDs.Get(LevelInteractKey.CutVines)
: InteractEventIDs.Get(ItemInteractKey.WrongTool);
var autoUseTools = SettingsService != null &&
SettingsService.Game != null &&
SettingsService.Game.AutoUseTools;
var equippedTool = PlayerManager.GetEquippedTool();
string message;
if (equippedTool == ToolID.None)
{
message = InteractEventIDs.Get(LevelInteractKey.CutVines);
}
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(LevelInteractKey.CutVines);
}
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

@@ -39,6 +39,11 @@ namespace BriarQueen.Game.Items.HoverZones
public CanvasGroup CanvasGroup;
public bool CanInteract()
{
return true;
}
protected void Awake()
{
if(CanvasGroup == null)

View File

@@ -1,7 +1,60 @@
using BriarQueen.Data.Identifiers;
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Events.Gameplay;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using BriarQueen.Framework.Managers.UI;
using Cysharp.Threading.Tasks;
namespace BriarQueen.Game.Items.Pickups.ChapterOne.LaxleyHouse
{
public class MantleClock
public class MantleClock : BaseItem
{
public override UICursorService.CursorStyle ApplicableCursorStyle =>
HasRetrievedHourHand
? UICursorService.CursorStyle.Inspect
: UICursorService.CursorStyle.Pickup;
public override string InteractableName => "Broken Mantle Clock";
private bool HasRetrievedHourHand =>
SaveManager.GetLevelFlag(LevelFlag.LaxleyHourHandRetrieved);
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (!CheckEmptyHands())
return;
if (item != null)
{
EventCoordinator.Publish(
new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
return;
}
if (HasRetrievedHourHand)
{
EventCoordinator.Publish(
new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.LaxleyHouseBrokenClock)));
return;
}
PlayerManager.CollectItem(ItemIDs.Get(ItemKey.LaxleyClockHourHand));
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.LooksImportant)));
EventCoordinator.Publish(new SelectedItemChangedEvent(null));
await OnInteracted();
}
protected override UniTask OnInteracted()
{
if (!HasRetrievedHourHand)
SaveManager.SetLevelFlag(LevelFlag.LaxleyHourHandRetrieved, true);
return UniTask.CompletedTask;
}
}
}

View File

@@ -1,7 +1,171 @@
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.Events.UI;
using BriarQueen.Framework.Managers.Hints.Data;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Services.Puzzles.Base;
using Cysharp.Threading.Tasks;
using MemoryPack;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Clock
{
public class LaxleyClockBasePuzzle
[Serializable]
[MemoryPackable]
public partial struct LaxleyClockPuzzleState
{
public bool HourHandPlaced;
public bool MinuteHandPlaced;
public int HourHandRotationStep;
public int MinuteHandRotationStep;
public bool IsSolved;
}
public class LaxleyClockBasePuzzle : BasePuzzle, IPuzzleStateful
{
private const int SolvedHourStep = 6;
private const int SolvedMinuteStep = 1;
[Header("Clock Face")]
[SerializeField]
private LaxleyClockFace _clockFace;
[Header("Puzzle State")]
[SerializeField]
private Image _background;
[SerializeField]
private Sprite _clockOpenSprite;
[SerializeField]
private List<BaseItem> _clockItems;
public override string PuzzleID => PuzzleIdentifiers.AllPuzzles[PuzzleKey.LaxleyClock];
public override string LevelName => "Grandfather Clock";
public override Dictionary<int, BaseHint> Hints { get; }
public bool IsCompleted => SaveManager.GetLevelFlag(LevelFlag.LaxleyClockSolved);
protected override async UniTask PostLoadInternal()
{
_clockFace.Initialise(this);
if (SaveManager.GetLevelFlag(LevelFlag.LaxleyClockSolved))
{
_clockFace.LockHands();
await OpenClock(false);
}
}
public async UniTask NotifyClockStateChanged()
{
if (IsCompleted)
return;
if (!_clockFace.AreBothHandsPlaced())
return;
if (_clockFace.HourHandRotationStep == SolvedHourStep &&
_clockFace.MinuteHandRotationStep == SolvedMinuteStep)
{
await CompletePuzzle();
}
}
public override async UniTask CompletePuzzle()
{
if (IsCompleted)
return;
SaveManager.SetLevelFlag(LevelFlag.LaxleyClockSolved, true);
SaveManager.SetPuzzleCompleted(PuzzleKey.LaxleyClock, true);
EventCoordinator.Publish(new FadeEvent(false, 0.5f));
await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
_clockFace.HideMechanism();
_clockFace.LockHands();
await OpenClock(true);
EventCoordinator.Publish(new UnlockAchievementEvent(AchievementID.LaxleyGrandfatherClockPuzzleSolved));
EventCoordinator.Publish(new FadeEvent(true, 0.5f));
await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
}
public UniTask<byte[]> CaptureState()
{
var state = new LaxleyClockPuzzleState
{
HourHandPlaced = _clockFace.HourHandPlaced,
MinuteHandPlaced = _clockFace.MinuteHandPlaced,
HourHandRotationStep = _clockFace.HourHandRotationStep,
MinuteHandRotationStep = _clockFace.MinuteHandRotationStep,
IsSolved = IsCompleted
};
byte[] data = MemoryPackSerializer.Serialize(state);
return UniTask.FromResult(data);
}
public async UniTask RestoreState(byte[] state)
{
_clockFace.Initialise(this);
if (state == null || state.Length == 0)
{
if (SaveManager.GetLevelFlag(LevelFlag.LaxleyClockSolved))
{
_clockFace.LockHands();
await OpenClock(false);
}
return;
}
LaxleyClockPuzzleState restored = MemoryPackSerializer.Deserialize<LaxleyClockPuzzleState>(state);
_clockFace.RestoreState(
restored.HourHandPlaced,
restored.MinuteHandPlaced,
restored.HourHandRotationStep,
restored.MinuteHandRotationStep,
restored.IsSolved);
if (restored.IsSolved || SaveManager.GetLevelFlag(LevelFlag.LaxleyClockSolved))
{
_clockFace.LockHands();
await OpenClock(false);
}
}
private async UniTask OpenClock(bool playAudio = false)
{
_background.sprite = _clockOpenSprite;
if (playAudio)
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.ClockOpening));
await UnlockItems();
}
private UniTask UnlockItems()
{
foreach (var item in _clockItems)
{
if (SaveManager.CurrentSave.CollectedItems.All(x => x.UniqueIdentifier != item.ItemData.UniqueID))
{
item.CanvasGroup.blocksRaycasts = true;
item.CanvasGroup.interactable = true;
item.CanvasGroup.alpha = 1;
}
}
return UniTask.CompletedTask;
}
}
}

View File

@@ -1,7 +1,192 @@
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 Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Clock
{
public class LaxleyClockFace
public class LaxleyClockFace : BaseItem
{
[Header("Hands")]
[SerializeField]
private LaxleyClockHand _hourHand;
[SerializeField]
private LaxleyClockHand _minuteHand;
[SerializeField]
private Image _hub;
private LaxleyClockBasePuzzle _owningPuzzle;
private bool _locked;
public bool HourHandPlaced => _hourHand != null && _hourHand.Placed;
public bool MinuteHandPlaced => _minuteHand != null && _minuteHand.Placed;
public int HourHandRotationStep => _hourHand != null ? _hourHand.CurrentRotationStep : 0;
public int MinuteHandRotationStep => _minuteHand != null ? _minuteHand.CurrentRotationStep : 0;
public override string InteractableName => "Clock Face";
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
public void Initialise(LaxleyClockBasePuzzle owningPuzzle)
{
_owningPuzzle = owningPuzzle;
_locked = false;
gameObject.SetActive(true);
if (_hub != null)
_hub.gameObject.SetActive(true);
_hourHand?.Initialise(this, _owningPuzzle);
_minuteHand?.Initialise(this, _owningPuzzle);
RefreshInteractionState();
}
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (!IsInteractable())
return;
if (item == null)
{
string message = string.Empty;
if (!HourHandPlaced && !MinuteHandPlaced)
{
message = InteractEventIDs.Get(EnvironmentInteractKey.LaxleyGrandfatherClockMissingBothHands);
}
else if (!HourHandPlaced && MinuteHandPlaced)
{
message = InteractEventIDs.Get(EnvironmentInteractKey.LaxleyGrandfatherClockMissingHourHand);
}
else if (HourHandPlaced && !MinuteHandPlaced)
{
message = InteractEventIDs.Get(EnvironmentInteractKey.LaxleyGrandfatherClockMissingMinuteHand);
}
if (!string.IsNullOrWhiteSpace(message))
EventCoordinator.Publish(new DisplayInteractEvent(message));
return;
}
bool isHourHandItem = item.UniqueID == ItemIDs.Get(ItemKey.LaxleyClockHourHand);
bool isMinuteHandItem = item.UniqueID == ItemIDs.Get(ItemKey.LaxleyClockMinuteHand);
if (!isHourHandItem && !isMinuteHandItem)
{
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
return;
}
await PlaceHand(item);
}
public bool AreBothHandsPlaced()
{
return HourHandPlaced && MinuteHandPlaced;
}
public void LockHands()
{
_locked = true;
_hourHand?.Lock();
_minuteHand?.Lock();
RefreshInteractionState();
}
public void HideMechanism()
{
_locked = true;
_hourHand?.Hide();
_minuteHand?.Hide();
if (_hub != null)
_hub.gameObject.SetActive(false);
gameObject.SetActive(false);
}
public void RestoreState(
bool hourPlaced,
bool minutePlaced,
int hourRotationStep,
int minuteRotationStep,
bool solved)
{
_locked = solved;
gameObject.SetActive(!solved);
if (solved)
{
_hourHand?.RestoreState(hourPlaced, hourRotationStep, true);
_minuteHand?.RestoreState(minutePlaced, minuteRotationStep, true);
HideMechanism();
return;
}
if (_hub != null)
_hub.gameObject.SetActive(true);
_hourHand?.RestoreState(hourPlaced, hourRotationStep, false);
_minuteHand?.RestoreState(minutePlaced, minuteRotationStep, false);
RefreshInteractionState();
}
internal async UniTask NotifyHandChanged()
{
RefreshInteractionState();
if (_owningPuzzle != null)
await _owningPuzzle.NotifyClockStateChanged();
}
private async UniTask PlaceHand(ItemDataSo item)
{
if (item.UniqueID == ItemIDs.Get(ItemKey.LaxleyClockHourHand))
{
await _hourHand.Place();
}
else if (item.UniqueID == ItemIDs.Get(ItemKey.LaxleyClockMinuteHand))
{
await _minuteHand.Place();
}
PlayerManager.RemoveItem(item);
RefreshInteractionState();
if (_owningPuzzle != null)
await _owningPuzzle.NotifyClockStateChanged();
}
private void RefreshInteractionState()
{
bool bothPlaced = AreBothHandsPlaced();
bool canPlaceHands = !_locked && !bothPlaced;
if (CanvasGroup != null)
{
CanvasGroup.blocksRaycasts = canPlaceHands;
CanvasGroup.interactable = canPlaceHands;
}
_hourHand?.SetCanRotate(!_locked && bothPlaced);
_minuteHand?.SetCanRotate(!_locked && bothPlaced);
}
private bool IsInteractable()
{
return !_locked && !AreBothHandsPlaced();
}
}
}

View File

@@ -1,7 +1,316 @@
using System.Threading;
using BriarQueen.Framework.Managers.Levels.Data;
using BriarQueen.Framework.Managers.Player.Data;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Clock
{
public class LaxleyClockHand
public class LaxleyClockHand : BaseItem
{
[Header("State")]
[SerializeField]
private bool _placed;
[SerializeField]
[Range(0, 11)]
private int _currentRotationStep;
[SerializeField]
private bool _locked;
[SerializeField]
private bool _isRotating;
[SerializeField]
private bool _canRotate;
[Header("Rotation")]
[SerializeField]
private float[] _rotationSteps = new float[12]
{
0f, // 12
-30f, // 1
-60f, // 2
-90f, // 3
-120f, // 4
-150f, // 5
-180f, // 6
-210f, // 7
-240f, // 8
-270f, // 9
-300f, // 10
-330f // 11
};
[SerializeField]
private float _rotateDuration = 0.15f;
[SerializeField]
private Ease _rotateEase = Ease.Linear;
[Header("Placement")]
[SerializeField]
private float _placeDuration = 0.2f;
[SerializeField]
private Ease _placeEase = Ease.OutSine;
[Header("Components")]
[SerializeField]
private GameObject _parentPivot;
[SerializeField]
private CanvasGroup _parentCanvasGroup;
[Header("Placement Behaviour")]
[SerializeField]
private bool _randomiseRotationOnFirstPlace = true;
private Sequence _placeSequence;
private Sequence _rotateSequence;
private CancellationTokenSource _placeCTS;
private CancellationTokenSource _rotateCTS;
private LaxleyClockFace _clockFace;
public bool Placed => _placed;
public int CurrentRotationStep => _currentRotationStep;
public void Initialise(LaxleyClockFace clockFace, LaxleyClockBasePuzzle owningPuzzle)
{
_clockFace = clockFace;
_canRotate = false;
if (_placed)
{
if (_parentPivot != null)
_parentPivot.SetActive(true);
if (_parentCanvasGroup != null)
_parentCanvasGroup.alpha = 1f;
ApplyRotationImmediate(_currentRotationStep);
}
else
{
if (_parentPivot != null)
_parentPivot.SetActive(false);
if (_parentCanvasGroup != null)
_parentCanvasGroup.alpha = 0f;
}
RefreshVisualState();
}
public override async UniTask OnInteract(ItemDataSo item = null)
{
if (!IsInteractable())
return;
await RotateToNextStep();
await _clockFace.NotifyHandChanged();
}
public async UniTask Place()
{
if (_placed)
return;
_placed = true;
_locked = false;
_canRotate = false;
CancelPlaceTween();
_placeCTS = new CancellationTokenSource();
if (_randomiseRotationOnFirstPlace)
_currentRotationStep = Random.Range(0, _rotationSteps.Length);
if (_parentPivot != null)
_parentPivot.SetActive(true);
ApplyRotationImmediate(_currentRotationStep);
if (_parentCanvasGroup != null)
{
_parentCanvasGroup.alpha = 0f;
_parentCanvasGroup.blocksRaycasts = false;
_parentCanvasGroup.interactable = false;
}
_placeSequence.Stop();
_placeSequence = Sequence.Create()
.Group(Tween.Alpha(_parentCanvasGroup, 1f, _placeDuration, _placeEase));
try
{
await _placeSequence.ToUniTask(cancellationToken: _placeCTS.Token);
}
catch
{
}
RefreshVisualState();
}
public void RestoreState(bool placed, int rotationStep, bool locked)
{
CancelPlaceTween();
CancelRotateTween();
_placed = placed;
_locked = locked;
_isRotating = false;
_canRotate = false;
_currentRotationStep = Mathf.Clamp(rotationStep, 0, 11);
if (_parentPivot != null)
_parentPivot.SetActive(_placed);
if (_placed)
{
ApplyRotationImmediate(_currentRotationStep);
if (_parentCanvasGroup != null)
_parentCanvasGroup.alpha = 1f;
}
else
{
if (_parentCanvasGroup != null)
_parentCanvasGroup.alpha = 0f;
}
RefreshVisualState();
}
public void SetCanRotate(bool canRotate)
{
_canRotate = canRotate;
RefreshVisualState();
}
public void Lock()
{
_locked = true;
_canRotate = false;
RefreshVisualState();
}
public void Hide()
{
CancelPlaceTween();
CancelRotateTween();
_canRotate = false;
_isRotating = false;
if (_parentCanvasGroup != null)
{
_parentCanvasGroup.blocksRaycasts = false;
_parentCanvasGroup.interactable = false;
_parentCanvasGroup.alpha = 0f;
}
if (_parentPivot != null)
_parentPivot.SetActive(false);
}
private async UniTask RotateToNextStep()
{
if (!IsInteractable())
return;
_isRotating = true;
RefreshVisualState();
CancelRotateTween();
_rotateCTS = new CancellationTokenSource();
_currentRotationStep = (_currentRotationStep + 1) % 12;
Vector3 targetEuler = _parentPivot.transform.localEulerAngles;
targetEuler.z = _rotationSteps[_currentRotationStep];
_rotateSequence.Stop();
_rotateSequence = Sequence.Create()
.Group(
Tween.LocalRotation(
_parentPivot.transform,
Quaternion.Euler(targetEuler),
_rotateDuration,
_rotateEase));
try
{
await _rotateSequence.ToUniTask(cancellationToken: _rotateCTS.Token);
}
catch
{
}
_isRotating = false;
RefreshVisualState();
}
private void ApplyRotationImmediate(int step)
{
if (_parentPivot == null)
return;
Vector3 euler = _parentPivot.transform.localEulerAngles;
euler.z = _rotationSteps[Mathf.Clamp(step, 0, 11)];
_parentPivot.transform.localEulerAngles = euler;
}
private void RefreshVisualState()
{
bool interactable = IsInteractable();
if (_parentCanvasGroup != null)
{
_parentCanvasGroup.blocksRaycasts = interactable;
_parentCanvasGroup.interactable = interactable;
}
}
private bool IsInteractable()
{
return _placed && !_locked && !_isRotating && _canRotate;
}
private void CancelPlaceTween()
{
_placeSequence.Stop();
if (_placeCTS != null)
{
_placeCTS.Cancel();
_placeCTS.Dispose();
_placeCTS = null;
}
}
private void CancelRotateTween()
{
_rotateSequence.Stop();
if (_rotateCTS != null)
{
_rotateCTS.Cancel();
_rotateCTS.Dispose();
_rotateCTS = null;
}
}
private void OnDestroy()
{
CancelPlaceTween();
CancelRotateTween();
}
}
}

View File

@@ -4,18 +4,18 @@ using System.Linq;
using AYellowpaper.SerializedCollections;
using BriarQueen.Data.Identifiers;
using BriarQueen.Data.IO.Saves;
using BriarQueen.Framework.Events.Progression;
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
namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Fireplace.LockboxPuzzle
{
[Serializable]
[MemoryPackable]
@@ -94,6 +94,7 @@ namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.LockboxOpening));
await OpenLockbox();
EventCoordinator.Publish(new UnlockAchievementEvent(AchievementID.FireplaceLockboxPuzzleBoxSolved));
EventCoordinator.Publish(new FadeEvent(true, 0.5f));
}
@@ -103,10 +104,10 @@ namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse
if (_backgroundImage != null)
_backgroundImage.sprite = _lockBoxOpenSprite;
await GenerateLockboxItems();
await UnlockLockboxItems();
}
private async UniTask GenerateLockboxItems()
private async UniTask UnlockLockboxItems()
{
foreach (var item in _lockboxItems)
{
@@ -119,10 +120,6 @@ namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse
item.CanvasGroup.blocksRaycasts = true;
item.CanvasGroup.interactable = true;
}
else
{
await DestructionService.Destroy(item.gameObject);
}
}
}

View File

@@ -7,7 +7,7 @@ using PrimeTween;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Game.Levels.ChapterOne.LaxleyHouse.FireplaceLockbox
namespace BriarQueen.Game.Puzzles.ChapterOne.LaxleyHouse.Fireplace.LockboxPuzzle
{
public class LockboxSlot : BaseItem
{

View File

@@ -1,5 +1,6 @@
using System;
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -82,10 +83,12 @@ namespace BriarQueen.UI.Codex
Location = location;
if (_label != null)
_label.text = string.IsNullOrWhiteSpace(displayText) ? location.ToString() : displayText;
_label.text = string.IsNullOrWhiteSpace(displayText) ? location.ToString().Prettify() : displayText.Prettify();
RefreshVisuals();
}
private void HandleClicked()
{

View File

@@ -38,6 +38,8 @@ namespace BriarQueen.UI.HUD
public UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.UseItem;
public string InteractableName => Item.ItemName;
public bool CanInteract() => true;
public UniTask EnterHover()
{
return UniTask.CompletedTask;