diff --git a/Assets/AppleHillsLightingSettings.lighting b/Assets/AppleHillsLightingSettings.lighting new file mode 100644 index 00000000..f3249802 --- /dev/null +++ b/Assets/AppleHillsLightingSettings.lighting @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!850595691 &4890085278179872738 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AppleHillsLightingSettings + serializedVersion: 9 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_LightmapSizeFixed: 0 + m_UseMipmapLimits: 1 + m_BakeResolution: 40 + m_Padding: 2 + m_LightmapCompression: 3 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_EnableWorkerProcessBaking: 1 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 2 + m_PVREnvironmentImportanceSampling: 1 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_RespectSceneVisibilityWhenBakingGI: 0 diff --git a/Assets/AppleHillsLightingSettings.lighting.meta b/Assets/AppleHillsLightingSettings.lighting.meta new file mode 100644 index 00000000..b60fa40c --- /dev/null +++ b/Assets/AppleHillsLightingSettings.lighting.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d13be91f5840cb447b407875cd9bbefc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 4890085278179872738 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AppleHillsRenderPipeline.asset b/Assets/AppleHillsRenderPipeline.asset index d485c10c..afeed58f 100644 --- a/Assets/AppleHillsRenderPipeline.asset +++ b/Assets/AppleHillsRenderPipeline.asset @@ -22,7 +22,7 @@ MonoBehaviour: m_RequireDepthTexture: 0 m_RequireOpaqueTexture: 0 m_OpaqueDownsampling: 1 - m_SupportsTerrainHoles: 1 + m_SupportsTerrainHoles: 0 m_SupportsHDR: 0 m_HDRColorBufferPrecision: 0 m_MSAA: 1 @@ -44,7 +44,7 @@ MonoBehaviour: m_MainLightRenderingMode: 0 m_MainLightShadowsSupported: 0 m_MainLightShadowmapResolution: 2048 - m_AdditionalLightsRenderingMode: 1 + m_AdditionalLightsRenderingMode: 0 m_AdditionalLightsPerObjectLimit: 4 m_AdditionalLightShadowsSupported: 0 m_AdditionalLightsShadowmapResolution: 2048 @@ -71,8 +71,8 @@ MonoBehaviour: m_AdditionalLightsCookieFormat: 3 m_UseSRPBatcher: 1 m_SupportsDynamicBatching: 0 - m_MixedLightingSupported: 1 - m_SupportsLightCookies: 1 + m_MixedLightingSupported: 0 + m_SupportsLightCookies: 0 m_SupportsLightLayers: 0 m_DebugLevel: 0 m_StoreActionsOptimization: 0 @@ -91,7 +91,7 @@ MonoBehaviour: m_LocalShadowsAtlasResolution: 256 m_MaxPixelLights: 0 m_ShadowAtlasResolution: 256 - m_VolumeFrameworkUpdateMode: 0 + m_VolumeFrameworkUpdateMode: 1 m_VolumeProfile: {fileID: 0} apvScenesData: obsoleteSceneBounds: diff --git a/Assets/AppleHillsRenderPipeline_Renderer.asset b/Assets/AppleHillsRenderPipeline_Renderer.asset index 381dea06..7be2267a 100644 --- a/Assets/AppleHillsRenderPipeline_Renderer.asset +++ b/Assets/AppleHillsRenderPipeline_Renderer.asset @@ -53,6 +53,6 @@ MonoBehaviour: m_CameraSortingLayerDownsamplingMethod: 0 m_MaxLightRenderTextureCount: 16 m_MaxShadowRenderTextureCount: 1 - m_PostProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} - m_DefaultMaterialType: 0 + m_PostProcessData: {fileID: 0} + m_DefaultMaterialType: 1 m_DefaultCustomMaterial: {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} diff --git a/Assets/Art/Animations/Characters/Pulver/ANIM_PulverCombine.anim b/Assets/Art/Animations/Characters/Pulver/ANIM_PulverCombine.anim index 3633aca1..2b3ff7b9 100644 --- a/Assets/Art/Animations/Characters/Pulver/ANIM_PulverCombine.anim +++ b/Assets/Art/Animations/Characters/Pulver/ANIM_PulverCombine.anim @@ -288,7 +288,7 @@ AnimationClip: m_AdditiveReferencePoseClip: {fileID: 0} m_AdditiveReferencePoseTime: 0 m_StartTime: 0 - m_StopTime: 2.3999999 + m_StopTime: 2.4 m_OrientationOffsetY: 0 m_Level: 0 m_CycleOffset: 0 @@ -370,4 +370,11 @@ AnimationClip: m_EulerEditorCurves: [] m_HasGenericRootTransform: 0 m_HasMotionFloatCurves: 0 - m_Events: [] + m_Events: + - time: 2.4 + functionName: OnStationaryAnimationComplete + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 diff --git a/Assets/Art/Animations/Characters/Pulver/Pulver_Cucumbatacc.anim b/Assets/Art/Animations/Characters/Pulver/Pulver_Cucumbatacc.anim index 1d9cc616..7f8f1366 100644 --- a/Assets/Art/Animations/Characters/Pulver/Pulver_Cucumbatacc.anim +++ b/Assets/Art/Animations/Characters/Pulver/Pulver_Cucumbatacc.anim @@ -336,4 +336,11 @@ AnimationClip: m_EulerEditorCurves: [] m_HasGenericRootTransform: 0 m_HasMotionFloatCurves: 0 - m_Events: [] + m_Events: + - time: 1.6666666 + functionName: OnStationaryAnimationComplete + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 diff --git a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset index a853574c..2da2d364 100644 --- a/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset +++ b/Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset @@ -26,3 +26,4 @@ MonoBehaviour: - {fileID: 3528960956969533010, guid: 53eea3840d3cde34a9768b8773a3a7e8, type: 3} - {fileID: 6895404274863911569, guid: 840f3d8a936b39a41b5896328a692005, type: 3} - {fileID: 3863019143023165617, guid: 774e30e3f0b1d0d49bad0c2abf11038a, type: 3} + - {fileID: 5034240524438268576, guid: b15ba9d3d508ef244b0eeb76404dc9de, type: 3} diff --git a/Assets/Dialogue/Anne Lise/AnaLiseInBushADialogue.dialoguegraph b/Assets/Dialogue/Anne Lise/AnaLiseInBushADialogue.dialoguegraph index 026e53f8..ca6227d8 100644 --- a/Assets/Dialogue/Anne Lise/AnaLiseInBushADialogue.dialoguegraph +++ b/Assets/Dialogue/Anne Lise/AnaLiseInBushADialogue.dialoguegraph @@ -307,6 +307,7 @@ MonoBehaviour: - LoopThroughIncorrectItemLines - ForbiddenItemDialogueContent - LoopThroughForbiddenItemLines + - ShouldAutoPlay m_ValueList: - rid: 4008004961314799713 - rid: 4008004961314799714 @@ -321,6 +322,7 @@ MonoBehaviour: - rid: 4008004961314799723 - rid: 4008004961314799724 - rid: 4008004961314799725 + - rid: 7545630068434796544 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -525,3 +527,7 @@ MonoBehaviour: - rid: 4008004961314799729 type: {class: EndNode, ns: Editor.Dialogue, asm: AppleHillsEditor} data: + - rid: 7545630068434796544 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 1 diff --git a/Assets/Dialogue/Anne Lise/AnaLiseInBushBDialogue.dialoguegraph b/Assets/Dialogue/Anne Lise/AnaLiseInBushBDialogue.dialoguegraph index b26396ad..9c04a252 100644 --- a/Assets/Dialogue/Anne Lise/AnaLiseInBushBDialogue.dialoguegraph +++ b/Assets/Dialogue/Anne Lise/AnaLiseInBushBDialogue.dialoguegraph @@ -168,6 +168,7 @@ MonoBehaviour: - LoopThroughIncorrectItemLines - ForbiddenItemDialogueContent - LoopThroughForbiddenItemLines + - ShouldAutoPlay m_ValueList: - rid: 4008004961314799803 - rid: 4008004961314799804 @@ -182,6 +183,7 @@ MonoBehaviour: - rid: 4008004961314799813 - rid: 4008004961314799814 - rid: 4008004961314799815 + - rid: 7545630068434796549 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -525,3 +527,7 @@ MonoBehaviour: type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} data: m_Value: AnneLise + - rid: 7545630068434796549 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 diff --git a/Assets/Dialogue/Anne Lise/AnaLiseInBushCDialogue.dialoguegraph b/Assets/Dialogue/Anne Lise/AnaLiseInBushCDialogue.dialoguegraph index 1ad4bf6a..64585a8b 100644 --- a/Assets/Dialogue/Anne Lise/AnaLiseInBushCDialogue.dialoguegraph +++ b/Assets/Dialogue/Anne Lise/AnaLiseInBushCDialogue.dialoguegraph @@ -168,6 +168,7 @@ MonoBehaviour: - LoopThroughIncorrectItemLines - ForbiddenItemDialogueContent - LoopThroughForbiddenItemLines + - ShouldAutoPlay m_ValueList: - rid: 4008004961314799847 - rid: 4008004961314799848 @@ -182,6 +183,7 @@ MonoBehaviour: - rid: 4008004961314799857 - rid: 4008004961314799858 - rid: 4008004961314799859 + - rid: 7545630068434796550 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -525,3 +527,7 @@ MonoBehaviour: type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} data: m_Value: AnneLise + - rid: 7545630068434796550 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 diff --git a/Assets/Dialogue/Anne Lise/AnaLiseInBushDDialogue.dialoguegraph b/Assets/Dialogue/Anne Lise/AnaLiseInBushDDialogue.dialoguegraph index 856d29b5..92808258 100644 --- a/Assets/Dialogue/Anne Lise/AnaLiseInBushDDialogue.dialoguegraph +++ b/Assets/Dialogue/Anne Lise/AnaLiseInBushDDialogue.dialoguegraph @@ -168,6 +168,7 @@ MonoBehaviour: - LoopThroughIncorrectItemLines - ForbiddenItemDialogueContent - LoopThroughForbiddenItemLines + - ShouldAutoPlay m_ValueList: - rid: 4008004961314799891 - rid: 4008004961314799892 @@ -182,6 +183,7 @@ MonoBehaviour: - rid: 4008004961314799901 - rid: 4008004961314799902 - rid: 4008004961314799903 + - rid: 7545630068434796545 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -525,3 +527,7 @@ MonoBehaviour: type: {class: 'Constant`1[[System.String, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} data: m_Value: AnneLise + - rid: 7545630068434796545 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 diff --git a/Assets/Dialogue/Gardener/GardenerDialogueGraph.dialoguegraph b/Assets/Dialogue/Gardener/GardenerDialogueGraph.dialoguegraph index 021bedd8..0964f571 100644 --- a/Assets/Dialogue/Gardener/GardenerDialogueGraph.dialoguegraph +++ b/Assets/Dialogue/Gardener/GardenerDialogueGraph.dialoguegraph @@ -42,12 +42,14 @@ MonoBehaviour: - RequiredPuzzleStep - DefaultDialogueContent - LoopThroughDefaultLines + - ShouldAutoPlay m_ValueList: - rid: 4008004961314799759 - rid: 4008004961314799760 - rid: 4008004961314799761 - rid: 4008004961314799762 - rid: 4008004961314799763 + - rid: 7545630068434796551 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -169,6 +171,10 @@ MonoBehaviour: _text: _image: {fileID: 1487011052474782424, guid: f489e2c9ce64ff34aa3c7a91a4edbd77, type: 3} _audio: {fileID: 8300000, guid: b4ba973891dad4749b465e9a07987e1a, type: 3} + - rid: 7545630068434796551 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 - rid: 7772910664224079994 type: {class: GraphModelImp, ns: Unity.GraphToolkit.Editor.Implementation, asm: Unity.GraphToolkit.Editor} data: diff --git a/Assets/Dialogue/TestAssDialogue.dialoguegraph b/Assets/Dialogue/TestAssDialogue.dialoguegraph index 13412bf8..45d8d87e 100644 --- a/Assets/Dialogue/TestAssDialogue.dialoguegraph +++ b/Assets/Dialogue/TestAssDialogue.dialoguegraph @@ -487,6 +487,7 @@ MonoBehaviour: - DefaultDialogueContent1 - DefaultDialogueContent2 - DefaultDialogueContent3 + - ShouldAutoPlay m_ValueList: - rid: 1226592702090707084 - rid: 1226592702090707085 @@ -495,6 +496,7 @@ MonoBehaviour: - rid: 7545629632211976304 - rid: 7545629632211976305 - rid: 7545629632211976306 + - rid: 7545630068434796546 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -689,12 +691,14 @@ MonoBehaviour: - RequiredResultItem - LoopThroughDefaultLines - DefaultDialogueContent + - ShouldAutoPlay m_ValueList: - rid: 1226592736610877524 - rid: 1226592736610877525 - rid: 1226592736610877526 - rid: 1226592736610877528 - rid: 7545629632211976309 + - rid: 7545630068434796547 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -895,6 +899,7 @@ MonoBehaviour: - DefaultDialogueContent - IncorrectItemDialogueContent - ForbiddenItemDialogueContent + - ShouldAutoPlay m_ValueList: - rid: 1226592736610877547 - rid: 1226592736610877548 @@ -909,6 +914,7 @@ MonoBehaviour: - rid: 7545629632211976311 - rid: 7545629632211976312 - rid: 7545629632211976313 + - rid: 7545630068434796548 m_InputPortInfos: expandedPortsById: m_KeyList: [] @@ -1212,3 +1218,15 @@ MonoBehaviour: _text: Yessssss, thanks! _image: {fileID: 0} _audio: {fileID: 0} + - rid: 7545630068434796546 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 + - rid: 7545630068434796547 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 + - rid: 7545630068434796548 + type: {class: 'Constant`1[[System.Boolean, mscorlib]]', ns: Unity.GraphToolkit.Editor, asm: Unity.GraphToolkit.Internal.Editor} + data: + m_Value: 0 diff --git a/Assets/Editor/Dialogue/DialogueGraphImporter.cs b/Assets/Editor/Dialogue/DialogueGraphImporter.cs index 3556912c..3008a70b 100644 --- a/Assets/Editor/Dialogue/DialogueGraphImporter.cs +++ b/Assets/Editor/Dialogue/DialogueGraphImporter.cs @@ -164,6 +164,8 @@ namespace Editor.Dialogue { runtimeNode.puzzleStepID = puzzleStep.stepId; } + + runtimeNode.shouldAutoPlay = GetPortValue(node.GetInputPortByName("ShouldAutoPlay")); } private void ProcessPickupNode(WaitOnPickup node, RuntimeDialogueNode runtimeNode) @@ -175,6 +177,8 @@ namespace Editor.Dialogue { runtimeNode.pickupItemID = pickup.itemId; } + + runtimeNode.shouldAutoPlay = GetPortValue(node.GetInputPortByName("ShouldAutoPlay")); } private void ProcessSlotNode(WaitOnSlot node, RuntimeDialogueNode runtimeNode) @@ -187,6 +191,8 @@ namespace Editor.Dialogue runtimeNode.slotItemID = slot.itemId; } + runtimeNode.shouldAutoPlay = GetPortValue(node.GetInputPortByName("ShouldAutoPlay")); + // Get line type and count options for incorrect items var incorrectItemLineTypeOption = node.GetNodeOptionByName("IncorrectItemDialogueLineType"); incorrectItemLineTypeOption.TryGetValue(out var incorrectItemLineType); @@ -296,6 +302,8 @@ namespace Editor.Dialogue { runtimeNode.combinationResultItemID = resultItem.itemId; } + + runtimeNode.shouldAutoPlay = GetPortValue(node.GetInputPortByName("ShouldAutoPlay")); } private T GetPortValue(IPort port) diff --git a/Assets/Editor/Dialogue/DialogueNodes.cs b/Assets/Editor/Dialogue/DialogueNodes.cs index 3c2c8e0d..e9f751d7 100644 --- a/Assets/Editor/Dialogue/DialogueNodes.cs +++ b/Assets/Editor/Dialogue/DialogueNodes.cs @@ -90,10 +90,12 @@ namespace Editor.Dialogue public class WaitOnPuzzleStep : DialogueNode { const string RequiredPuzzleStep = "RequiredPuzzleStep"; + const string ShouldAutoPlayOptionName = "ShouldAutoPlay"; protected override void OnDefinePorts(IPortDefinitionContext context) { context.AddInputPort(RequiredPuzzleStep).WithDisplayName("Required Puzzle Step").Build(); + context.AddInputPort(ShouldAutoPlayOptionName).WithDisplayName("Should Auto-Play?").Build(); base.OnDefinePorts(context); } @@ -103,10 +105,12 @@ namespace Editor.Dialogue public class WaitOnPickup : DialogueNode { const string RequiredPickupsOptionName = "RequiredPickup"; + const string ShouldAutoPlayOptionName = "ShouldAutoPlay"; protected override void OnDefinePorts(IPortDefinitionContext context) { context.AddInputPort(RequiredPickupsOptionName).WithDisplayName("Required Pickup").Build(); + context.AddInputPort(ShouldAutoPlayOptionName).WithDisplayName("Should Auto-Play?").Build(); base.OnDefinePorts(context); } @@ -116,6 +120,7 @@ namespace Editor.Dialogue public class WaitOnSlot : DialogueNode { const string RequiredSlotOptionName = "RequiredSlot"; + const string ShouldAutoPlayOptionName = "ShouldAutoPlay"; const string IncorrectItemLineTypeOptionName = "IncorrectItemDialogueLineType"; const string IncorrectItemNoLinesOptionName = "IncorrectItemNoLines"; const string LoopThroughIncorrectItemLinesOptionName = "LoopThroughIncorrectItemLines"; @@ -156,6 +161,7 @@ namespace Editor.Dialogue protected override void OnDefinePorts(IPortDefinitionContext context) { context.AddInputPort(RequiredSlotOptionName).WithDisplayName("Required Slot").Build(); + context.AddInputPort(ShouldAutoPlayOptionName).WithDisplayName("Should Auto-Play?").Build(); base.OnDefinePorts(context); @@ -219,10 +225,12 @@ namespace Editor.Dialogue public class WaitOnCombination : DialogueNode { const string RequiredResultItemOptionName = "RequiredResultItem"; + const string ShouldAutoPlayOptionName = "ShouldAutoPlay"; protected override void OnDefinePorts(IPortDefinitionContext context) { context.AddInputPort(RequiredResultItemOptionName).WithDisplayName("Required Result Item").Build(); + context.AddInputPort(ShouldAutoPlayOptionName).WithDisplayName("Should Auto-Play?").Build(); base.OnDefinePorts(context); } diff --git a/Assets/Editor/Tools/ClearSavesMenuItem.cs b/Assets/Editor/Tools/ClearSavesMenuItem.cs new file mode 100644 index 00000000..8699d5b6 --- /dev/null +++ b/Assets/Editor/Tools/ClearSavesMenuItem.cs @@ -0,0 +1,63 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace Editor.Tools +{ + /// + /// Editor utility to clear all save data from the SaveLoadManager's save folder + /// + public static class ClearSavesMenuItem + { + [MenuItem("AppleHills/Clear Saves")] + private static void ClearSaves() + { + // Construct the save folder path (matches SaveLoadManager.DefaultSaveFolder) + string saveFolder = Path.Combine(Application.persistentDataPath, "GameSaves"); + + if (!Directory.Exists(saveFolder)) + { + Debug.Log($"[ClearSaves] Save folder does not exist: {saveFolder}"); + EditorUtility.DisplayDialog("Clear Saves", "No save data found to clear.", "OK"); + return; + } + + // Confirm with the user + bool confirmed = EditorUtility.DisplayDialog( + "Clear Saves", + $"Are you sure you want to delete all save data?\n\nFolder: {saveFolder}", + "Delete All", + "Cancel" + ); + + if (!confirmed) + { + Debug.Log("[ClearSaves] User cancelled save deletion"); + return; + } + + try + { + // Delete all files in the save folder + string[] files = Directory.GetFiles(saveFolder); + int deletedCount = 0; + + foreach (string file in files) + { + File.Delete(file); + deletedCount++; + Debug.Log($"[ClearSaves] Deleted: {file}"); + } + + Debug.Log($"[ClearSaves] Successfully deleted {deletedCount} save file(s)"); + EditorUtility.DisplayDialog("Clear Saves", $"Successfully deleted {deletedCount} save file(s).", "OK"); + } + catch (System.Exception ex) + { + Debug.LogError($"[ClearSaves] Failed to clear saves: {ex}"); + EditorUtility.DisplayDialog("Clear Saves", $"Error clearing saves:\n{ex.Message}", "OK"); + } + } + } +} + diff --git a/Assets/Editor/Tools/ClearSavesMenuItem.cs.meta b/Assets/Editor/Tools/ClearSavesMenuItem.cs.meta new file mode 100644 index 00000000..9e70859c --- /dev/null +++ b/Assets/Editor/Tools/ClearSavesMenuItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 747ab9e66b014771bc1486f9ab073f90 +timeCreated: 1761569785 \ No newline at end of file diff --git a/Assets/Editor/Tools/SceneBrowserWindow.cs b/Assets/Editor/Tools/SceneBrowserWindow.cs index 30166bba..42ef3bf3 100644 --- a/Assets/Editor/Tools/SceneBrowserWindow.cs +++ b/Assets/Editor/Tools/SceneBrowserWindow.cs @@ -62,19 +62,28 @@ public class SceneBrowserWindow : EditorWindow if (GUILayout.Button("Refresh")) RefreshScenes(); _scroll = EditorGUILayout.BeginScrollView(_scroll); + + // Create a safe copy to avoid collection modification during enumeration + var foldersCopy = _scenesByFolder.Keys.ToList(); + // Top-level scenes - if (_scenesByFolder.ContainsKey("")) + if (foldersCopy.Contains("") && _scenesByFolder.ContainsKey("")) { - foreach (var scene in _scenesByFolder[""]) + var topLevelScenes = _scenesByFolder[""].ToList(); + foreach (var scene in topLevelScenes) DrawSceneRow(scene); EditorGUILayout.Space(); } + // Subfolders - foreach (var kvp in _scenesByFolder) + foreach (var folderKey in foldersCopy) { - if (string.IsNullOrEmpty(kvp.Key)) continue; - EditorGUILayout.LabelField(kvp.Key, EditorStyles.boldLabel); - foreach (var scene in kvp.Value) + if (string.IsNullOrEmpty(folderKey)) continue; + if (!_scenesByFolder.ContainsKey(folderKey)) continue; + + EditorGUILayout.LabelField(folderKey, EditorStyles.boldLabel); + var scenesInFolder = _scenesByFolder[folderKey].ToList(); + foreach (var scene in scenesInFolder) DrawSceneRow(scene); EditorGUILayout.Space(); } diff --git a/Assets/Playables/PulverCucumberSmack1.playable b/Assets/Playables/PulverCucumberSmack1.playable index 29b5b6a0..4703094c 100644 --- a/Assets/Playables/PulverCucumberSmack1.playable +++ b/Assets/Playables/PulverCucumberSmack1.playable @@ -12,6 +12,22 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: fde0d25a170598d46a0b9dc16b4527a5, type: 3} m_Name: ActivationPlayableAsset m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.ActivationPlayableAsset +--- !u!114 &-7800811525781169865 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 15c38f6fa1940124db1ab7f6fe7268d1, type: 3} + m_Name: Signal Emitter + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalEmitter + m_Time: 0 + m_Retroactive: 0 + m_EmitOnce: 0 + m_Asset: {fileID: 11400000, guid: 2a32e2abdb44cb1469e5c6b9a25f98bc, type: 2} --- !u!114 &-7584736085941489071 MonoBehaviour: m_ObjectHideFlags: 1 @@ -139,6 +155,22 @@ MonoBehaviour: m_bufferingTime: 0.1 m_ClipProperties: volume: 0.683 +--- !u!114 &-5035745444053599677 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 15c38f6fa1940124db1ab7f6fe7268d1, type: 3} + m_Name: Signal Emitter + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalEmitter + m_Time: 1.65 + m_Retroactive: 0 + m_EmitOnce: 0 + m_Asset: {fileID: 11400000, guid: 2ec36cde76b6baf4299c0dd9ab54714e, type: 2} --- !u!114 &-4664548104421960294 MonoBehaviour: m_ObjectHideFlags: 1 @@ -491,12 +523,13 @@ MonoBehaviour: - {fileID: 5194333105783184030} - {fileID: 8484886637748558501} - {fileID: 8776627858148209300} + - {fileID: 6964431640287062015} m_FixedDuration: 0 m_EditorSettings: m_Framerate: 60 m_ScenePreview: 1 m_DurationMode: 0 - m_MarkerTrack: {fileID: 0} + m_MarkerTrack: {fileID: 7948415667162652308} --- !u!114 &297111036028248502 MonoBehaviour: m_ObjectHideFlags: 1 @@ -746,6 +779,54 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: fde0d25a170598d46a0b9dc16b4527a5, type: 3} m_Name: ActivationPlayableAsset m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.ActivationPlayableAsset +--- !u!114 &6964431640287062015 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b46e36075dd1c124a8422c228e75e1fb, type: 3} + m_Name: Signal Track + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalTrack + m_Version: 3 + m_AnimClip: {fileID: 0} + m_Locked: 0 + m_Muted: 0 + m_CustomPlayableFullTypename: + m_Curves: {fileID: 0} + m_Parent: {fileID: 11400000} + m_Children: [] + m_Clips: [] + m_Markers: + m_Objects: + - {fileID: -7800811525781169865} + - {fileID: -5035745444053599677} +--- !u!114 &7948415667162652308 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a16748d9461eae46a725db9776d5390, type: 3} + m_Name: Markers + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.MarkerTrack + m_Version: 3 + m_AnimClip: {fileID: 0} + m_Locked: 0 + m_Muted: 0 + m_CustomPlayableFullTypename: + m_Curves: {fileID: 0} + m_Parent: {fileID: 11400000} + m_Children: [] + m_Clips: [] + m_Markers: + m_Objects: [] --- !u!114 &8484886637748558501 MonoBehaviour: m_ObjectHideFlags: 1 diff --git a/Assets/Prefabs/Characters/PulverCharacter.prefab b/Assets/Prefabs/Characters/PulverCharacter.prefab index ce5857ba..fd998159 100644 --- a/Assets/Prefabs/Characters/PulverCharacter.prefab +++ b/Assets/Prefabs/Characters/PulverCharacter.prefab @@ -14,6 +14,7 @@ GameObject: - component: {fileID: 8947209170748834035} - component: {fileID: 7852204877518954380} - component: {fileID: 1621671461027776358} + - component: {fileID: 7002695641417196458} m_Layer: 8 m_Name: PulverCharacter m_TagString: Pulver @@ -184,6 +185,51 @@ MonoBehaviour: bezierTangentLength: 0.4 offset: 0.2 factor: 0.1 +--- !u!114 &7002695641417196458 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1102400833121127473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e52de21a22b6dd44c9cc19f810c65059, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalReceiver + m_Events: + m_Signals: + - {fileID: 11400000, guid: 2a32e2abdb44cb1469e5c6b9a25f98bc, type: 2} + - {fileID: 11400000, guid: 2ec36cde76b6baf4299c0dd9ab54714e, type: 2} + m_Events: + - m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 3435632802124758411} + m_TargetAssemblyTypeName: FollowerController, AppleHillsScripts + m_MethodName: PauseMovement + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + - m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 3435632802124758411} + m_TargetAssemblyTypeName: FollowerController, AppleHillsScripts + m_MethodName: ResumeMovement + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 --- !u!1 &5934518940303293264 GameObject: m_ObjectHideFlags: 0 @@ -338,8 +384,29 @@ PrefabInstance: - {fileID: 2732140975288177209, guid: e3b439d398cffdd4194cdb360a46c5a6, type: 3} m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 1102468210854536367, guid: e3b439d398cffdd4194cdb360a46c5a6, type: 3} + insertIndex: -1 + addedObject: {fileID: 3454096590710680423} m_SourcePrefab: {fileID: 100100000, guid: e3b439d398cffdd4194cdb360a46c5a6, type: 3} +--- !u!1 &780600094299918916 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 1102468210854536367, guid: e3b439d398cffdd4194cdb360a46c5a6, type: 3} + m_PrefabInstance: {fileID: 403634400421951211} + m_PrefabAsset: {fileID: 0} +--- !u!114 &3454096590710680423 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 780600094299918916} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 39f2e64420884f11a9022fea7b3be5d0, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::FollowerAnimationEventBridge + followerController: {fileID: 3435632802124758411} --- !u!4 &6418503932309983819 stripped Transform: m_CorrespondingSourceObject: {fileID: 6668392923879433376, guid: e3b439d398cffdd4194cdb360a46c5a6, type: 3} diff --git a/Assets/Prefabs/Managers/SaveLoadManager.prefab b/Assets/Prefabs/Managers/SaveLoadManager.prefab new file mode 100644 index 00000000..4782317f --- /dev/null +++ b/Assets/Prefabs/Managers/SaveLoadManager.prefab @@ -0,0 +1,48 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &5034240524438268576 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 536416456044738252} + - component: {fileID: 6631817730171642502} + m_Layer: 0 + m_Name: SaveLoadManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &536416456044738252 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5034240524438268576} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &6631817730171642502 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5034240524438268576} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a1731f4790be4ec3bab71506427768d7, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.SaveLoadManager + currentSaveData: + playedDivingTutorial: 0 diff --git a/Assets/Prefabs/Managers/SaveLoadManager.prefab.meta b/Assets/Prefabs/Managers/SaveLoadManager.prefab.meta new file mode 100644 index 00000000..4f641a8d --- /dev/null +++ b/Assets/Prefabs/Managers/SaveLoadManager.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b15ba9d3d508ef244b0eeb76404dc9de +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Levels/Quarry.unity b/Assets/Scenes/Levels/Quarry.unity index 42dca1ee..a6d47f7b 100644 --- a/Assets/Scenes/Levels/Quarry.unity +++ b/Assets/Scenes/Levels/Quarry.unity @@ -466228,6 +466228,17 @@ PrefabInstance: m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 0bbded61e58193848ac59c8eea761bcc, type: 3} +--- !u!114 &1760588396 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7002695641417196458, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3} + m_PrefabInstance: {fileID: 1363194738} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e52de21a22b6dd44c9cc19f810c65059, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalReceiver --- !u!1 &1761890788 GameObject: m_ObjectHideFlags: 0 @@ -477381,7 +477392,7 @@ PrefabInstance: objectReference: {fileID: 11400000, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} - target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} propertyPath: m_SceneBindings.Array.size - value: 13 + value: 14 objectReference: {fileID: 0} - target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} propertyPath: m_SceneBindings.Array.data[3].key @@ -477423,6 +477434,10 @@ PrefabInstance: propertyPath: m_SceneBindings.Array.data[12].key value: objectReference: {fileID: 8776627858148209300, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} + - target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} + propertyPath: m_SceneBindings.Array.data[13].key + value: + objectReference: {fileID: 6964431640287062015, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} - target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} propertyPath: m_SceneBindings.Array.data[0].value value: @@ -477467,6 +477482,10 @@ PrefabInstance: propertyPath: m_SceneBindings.Array.data[12].value value: objectReference: {fileID: 781815198} + - target: {fileID: 1569498917964935965, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} + propertyPath: m_SceneBindings.Array.data[13].value + value: + objectReference: {fileID: 1760588396} - target: {fileID: 2084964592986606867, guid: c36b48a324dcaef4cb5ee0f8ca57f0d6, type: 3} propertyPath: m_LocalPosition.y value: 1.68 diff --git a/Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs b/Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs index 8b2846f7..212ed8e0 100644 --- a/Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs +++ b/Assets/Scripts/AppleHillsCamera/EdgeAnchor.cs @@ -202,7 +202,7 @@ namespace AppleHillsCamera private void FindCameraAdapter() { // Try to find the camera adapter in the scene - var adapters = FindObjectsOfType(); + var adapters = FindObjectsByType(FindObjectsSortMode.None); if (adapters != null && adapters.Length > 0) { // Prioritize any adapter that's on the same camera we're using diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index a81b7940..f7e26347 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -123,8 +123,12 @@ namespace Core /// True to pause; false to resume private void ApplyPause(bool shouldPause) { - // TODO: Do we want to stop time? - // Time.timeScale = shouldPause ? 0f : 1f; + // Only affect timescale if debug setting allows it + var debugSettings = GetDeveloperSettings(); + if (debugSettings != null && debugSettings.PauseTimeOnPauseGame) + { + Time.timeScale = shouldPause ? 0f : 1f; + } // Notify registered components foreach (var component in _pausableComponents) @@ -213,8 +217,9 @@ namespace Core // Load developer settings var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings(); + var debugSettings = DeveloperSettingsProvider.Instance.GetSettings(); - _developerSettingsLoaded = divingDevSettings != null; + _developerSettingsLoaded = divingDevSettings != null && debugSettings != null; if (_developerSettingsLoaded) { diff --git a/Assets/Scripts/Core/SaveLoad.meta b/Assets/Scripts/Core/SaveLoad.meta new file mode 100644 index 00000000..554eb856 --- /dev/null +++ b/Assets/Scripts/Core/SaveLoad.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0541e0e1c3dd41f7a61773e4bc2c64ed +timeCreated: 1761553949 \ No newline at end of file diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs b/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs new file mode 100644 index 00000000..6ca0fb49 --- /dev/null +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Core.SaveLoad +{ + [System.Serializable] + public class SaveLoadData + { + public bool playedDivingTutorial = false; + + // Snapshot of the player's card collection (MVP) + public CardCollectionState cardCollection; + } + + // Minimal DTOs for card persistence + [System.Serializable] + public class CardCollectionState + { + public int boosterPackCount; + public List cards = new List(); + } + + [System.Serializable] + public class SavedCardEntry + { + public string definitionId; + public AppleHills.Data.CardSystem.CardRarity rarity; + public int copiesOwned; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs.meta b/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs.meta new file mode 100644 index 00000000..37a46769 --- /dev/null +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1704f1fc7e6b4a398f39fb86cec265f8 +timeCreated: 1761553972 \ No newline at end of file diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs new file mode 100644 index 00000000..be56dde9 --- /dev/null +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections; +using System.IO; +using Bootstrap; +using UnityEngine; + +namespace Core.SaveLoad +{ + /// + /// Simple Save/Load manager that follows the project's bootstrap pattern. + /// - Singleton instance + /// - Registers a post-boot init action with BootCompletionService + /// - Exposes simple async Save/Load methods (PlayerPrefs-backed placeholder) + /// - Fires events on completion + /// This is intended as boilerplate to be expanded with a real serialization backend. + /// + public class SaveLoadManager : MonoBehaviour + { + private static SaveLoadManager _instance; + public static SaveLoadManager Instance => _instance; + + // Path + private static string DefaultSaveFolder => Path.Combine(Application.persistentDataPath, "GameSaves"); + public SaveLoadData currentSaveData; + + // State + public bool IsSaving { get; private set; } + public bool IsLoading { get; private set; } + public bool IsSaveDataLoaded { get; private set; } + + // Events + public event Action OnSaveCompleted; + public event Action OnLoadCompleted; + + void Awake() + { + _instance = this; + IsSaveDataLoaded = false; + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void Start() + { + Load(); + } + + private void OnApplicationQuit() + { + Save(); + } + + private void InitializePostBoot() + { + Logging.Debug("[SaveLoadManager] Post-boot initialization complete"); + } + + void OnDestroy() + { + if (_instance == this) + _instance = null; + } + + private string GetFilePath(string slot) + { + return Path.Combine(DefaultSaveFolder, $"save_{slot}.json"); + } + + /// + /// Entry point to save to a named slot. Starts an async coroutine that writes to PlayerPrefs + /// (placeholder behavior). Fires OnSaveCompleted when finished. + /// + public void Save(string slot = "default") + { + if (IsSaving) + { + Logging.Warning("[SaveLoadManager] Save called while another save is in progress"); + return; + } + + StartCoroutine(SaveAsync(slot)); + } + + /// + /// Entry point to load from a named slot. Starts an async coroutine that reads from PlayerPrefs + /// (placeholder behavior). Fires OnLoadCompleted when finished. + /// + public void Load(string slot = "default") + { + if (IsLoading) + { + Logging.Warning("[SaveLoadManager] Load called while another load is in progress"); + return; + } + + StartCoroutine(LoadAsync(slot)); + } + + // TODO: This is stupid overkill, but over verbose logging is king for now + private IEnumerator SaveAsync(string slot) + { + Logging.Debug($"[SaveLoadManager] Starting save for slot '{slot}'"); + IsSaving = true; + + string path = GetFilePath(slot); + string tempPath = path + ".tmp"; + string json = null; + bool prepFailed = false; + + // Prep phase: ensure folder exists and serialize (no yields allowed inside try/catch) + try + { + Directory.CreateDirectory(DefaultSaveFolder); + + if (currentSaveData == null) + { + Logging.Debug("[SaveLoadManager] currentSaveData is null, creating default instance before saving"); + currentSaveData = new SaveLoadData(); + } + + // Pull latest card collection snapshot from CardSystem before serializing (don't overwrite with null) + if (Data.CardSystem.CardSystemManager.Instance != null) + { + currentSaveData.cardCollection = Data.CardSystem.CardSystemManager.Instance.ExportCardCollectionState(); + } + + json = JsonUtility.ToJson(currentSaveData, true); + } + catch (Exception ex) + { + Logging.Warning($"[SaveLoadManager] Exception during save prep for slot '{slot}': {ex}"); + prepFailed = true; + } + + if (prepFailed || string.IsNullOrEmpty(json)) + { + // Ensure we clean up state and notify listeners outside of the try/catch + IsSaving = false; + OnSaveCompleted?.Invoke(slot); + Logging.Warning($"[SaveLoadManager] Aborting save for slot '{slot}' due to prep failure"); + yield break; + } + + // Write phase: perform IO and atomic move with fallback + try + { + File.WriteAllText(tempPath, json); + + try + { + if (File.Exists(path)) + File.Delete(path); + File.Move(tempPath, path); + } + catch (Exception moveEx) + { + Logging.Warning($"[SaveLoadManager] Atomic move failed for '{path}', attempting overwrite: {moveEx}"); + File.Copy(tempPath, path, true); + File.Delete(tempPath); + } + + Logging.Debug($"[SaveLoadManager] Save data written to '{path}'"); + } + catch (Exception ex) + { + Logging.Warning($"[SaveLoadManager] Exception while saving slot '{slot}': {ex}"); + } + finally + { + IsSaving = false; + OnSaveCompleted?.Invoke(slot); + Debug.Log($"[SaveLoadManager] Save completed for slot '{slot}'"); + } + } + + // TODO: This is stupid overkill, but over verbose logging is king for now + private IEnumerator LoadAsync(string slot) + { + Logging.Debug($"[SaveLoadManager] Starting load for slot '{slot}'"); + IsLoading = true; + string path = GetFilePath(slot); + + // Fast-path: no save file + if (!File.Exists(path)) + { + Logging.Debug($"[SaveLoadManager] No save found at '{path}', creating defaults"); + currentSaveData = new SaveLoadData(); + + // Simulate async operation and finish + yield return null; + + IsLoading = false; + IsSaveDataLoaded = true; + OnLoadCompleted?.Invoke(slot); + yield break; + } + + // Simulate async operation (optional) + yield return null; + + try + { + string json = File.ReadAllText(path); + + if (string.IsNullOrEmpty(json)) + { + Logging.Warning($"[SaveLoadManager] Save file at '{path}' is empty. Creating defaults."); + currentSaveData = new SaveLoadData(); + } + else + { + // Attempt to deserialize; if it fails or returns null, fall back to defaults + var loaded = JsonUtility.FromJson(json); + if (loaded == null) + { + Logging.Warning($"[SaveLoadManager] Deserialized save data was null for slot '{slot}'. Creating defaults."); + currentSaveData = new SaveLoadData(); + } + else + { + currentSaveData = loaded; + } + } + } + catch (Exception ex) + { + Logging.Warning($"[SaveLoadManager] Exception while reading/deserializing save file at '{path}': {ex}"); + currentSaveData = new SaveLoadData(); + } + finally + { + IsLoading = false; + IsSaveDataLoaded = true; + OnLoadCompleted?.Invoke(slot); + Logging.Debug($"[SaveLoadManager] Load completed for slot '{slot}'"); + } + } + } +} diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs.meta b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs.meta new file mode 100644 index 00000000..cc7d84cd --- /dev/null +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a1731f4790be4ec3bab71506427768d7 +timeCreated: 1761553854 \ No newline at end of file diff --git a/Assets/Scripts/Core/Settings/Developer/DebugSettings.cs b/Assets/Scripts/Core/Settings/Developer/DebugSettings.cs index ed9c3789..9f270d36 100644 --- a/Assets/Scripts/Core/Settings/Developer/DebugSettings.cs +++ b/Assets/Scripts/Core/Settings/Developer/DebugSettings.cs @@ -25,8 +25,13 @@ namespace AppleHills.Core.Settings [Tooltip("Should debug messages be show on screen in Editor")] [SerializeField] private bool showDebugUiMessages = false; + [Header("Game Behavior Options")] + [Tooltip("Should Time.timeScale be set to 0 when the game is paused")] + [SerializeField] private bool pauseTimeOnPauseGame = true; + // Property getters public bool ShowDebugUiMessages => showDebugUiMessages; + public bool PauseTimeOnPauseGame => pauseTimeOnPauseGame; public override void OnValidate() { diff --git a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs index 7fe77c27..faa891a6 100644 --- a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs +++ b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs @@ -4,6 +4,7 @@ using System.Linq; using AppleHills.Data.CardSystem; using Bootstrap; using Core; +using Core.SaveLoad; using UnityEngine; #if UNITY_EDITOR using UnityEditor; @@ -36,6 +37,9 @@ namespace Data.CardSystem public event Action OnCardRarityUpgraded; public event Action OnBoosterCountChanged; + // Keep a reference to unsubscribe safely + private Action _onSaveDataLoadedHandler; + private void Awake() { _instance = this; @@ -49,9 +53,6 @@ namespace Data.CardSystem // Load card definitions from Addressables LoadCardDefinitionsFromAddressables(); - // Build lookup dictionary - BuildDefinitionLookup(); - Logging.Debug("[CardSystemManager] Post-boot initialization complete"); } @@ -87,9 +88,54 @@ namespace Data.CardSystem } } } + + // Build lookup now that cards are loaded + BuildDefinitionLookup(); + + // Apply saved state when save data is available + if (SaveLoadManager.Instance != null) + { + if (SaveLoadManager.Instance.IsSaveDataLoaded) + { + ApplySavedCardStateIfAvailable(); + } + else + { + SaveLoadManager.Instance.OnLoadCompleted += OnSaveDataLoadedHandler; + } + } } } - + + private void OnDestroy() + { + if (SaveLoadManager.Instance != null) + { + SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler; + } + } + + // Apply saved state if present in the SaveLoadManager + private void ApplySavedCardStateIfAvailable() + { + var data = SaveLoadManager.Instance?.currentSaveData; + if (data?.cardCollection != null) + { + ApplyCardCollectionState(data.cardCollection); + Logging.Debug("[CardSystemManager] Applied saved card collection state after loading definitions"); + } + } + + // Event handler for when save data load completes + private void OnSaveDataLoadedHandler(string slot) + { + ApplySavedCardStateIfAvailable(); + if (SaveLoadManager.Instance != null) + { + SaveLoadManager.Instance.OnLoadCompleted -= OnSaveDataLoadedHandler; + } + } + /// /// Builds a lookup dictionary for quick access to card definitions by ID /// @@ -383,5 +429,58 @@ namespace Data.CardSystem return (float)collectedOfRarity / totalOfRarity * 100f; } + + /// + /// Export current card collection to a serializable snapshot + /// + public CardCollectionState ExportCardCollectionState() + { + var state = new CardCollectionState + { + boosterPackCount = playerInventory.BoosterPackCount, + cards = new List() + }; + + foreach (var card in playerInventory.CollectedCards.Values) + { + if (string.IsNullOrEmpty(card.DefinitionId)) continue; + state.cards.Add(new SavedCardEntry + { + definitionId = card.DefinitionId, + rarity = card.Rarity, + copiesOwned = card.CopiesOwned + }); + } + return state; + } + + /// + /// Apply a previously saved snapshot to the runtime inventory + /// + public void ApplyCardCollectionState(CardCollectionState state) + { + if (state == null) return; + + playerInventory.ClearAllCards(); + playerInventory.BoosterPackCount = state.boosterPackCount; + OnBoosterCountChanged?.Invoke(playerInventory.BoosterPackCount); + + foreach (var entry in state.cards) + { + if (string.IsNullOrEmpty(entry.definitionId)) continue; + if (_definitionLookup.TryGetValue(entry.definitionId, out var def)) + { + // Create from definition to ensure links, then overwrite runtime fields + var cd = def.CreateCardData(); + cd.Rarity = entry.rarity; + cd.CopiesOwned = entry.copiesOwned; + playerInventory.AddCard(cd); + } + else + { + Logging.Warning($"[CardSystemManager] Saved card definition not found: {entry.definitionId}"); + } + } + } } } diff --git a/Assets/Scripts/Dialogue/DialogueComponent.cs b/Assets/Scripts/Dialogue/DialogueComponent.cs index 35e90d53..7ef34822 100644 --- a/Assets/Scripts/Dialogue/DialogueComponent.cs +++ b/Assets/Scripts/Dialogue/DialogueComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using Bootstrap; using Core; using Interactions; using UnityEngine; @@ -46,23 +47,6 @@ namespace Dialogue Debug.LogError("SpeechBubble component is missing on Dialogue Component"); } - // Register for global events - if (PuzzleManager.Instance != null) - PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted; - - if (ItemManager.Instance != null) - { - ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp; - ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted; - ItemManager.Instance.OnIncorrectItemSlotted += OnAnyIncorrectItemSlotted; - ItemManager.Instance.OnForbiddenItemSlotted += OnAnyForbiddenItemSlotted; - ItemManager.Instance.OnItemSlotCleared += OnAnyItemSlotCleared; - ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined; - } - - // Auto-start the dialogue - // StartDialogue(); - var interactable = GetComponent(); if (interactable != null) { @@ -74,6 +58,20 @@ namespace Dialogue { speechBubble.UpdatePromptVisibility(HasAnyLines()); } + + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // Register for global events + PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted; + ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp; + ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted; + ItemManager.Instance.OnIncorrectItemSlotted += OnAnyIncorrectItemSlotted; + ItemManager.Instance.OnForbiddenItemSlotted += OnAnyForbiddenItemSlotted; + ItemManager.Instance.OnItemSlotCleared += OnAnyItemSlotCleared; + ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined; } private void OnCharacterArrived() @@ -457,6 +455,12 @@ namespace Dialogue // Global event handlers private void OnAnyPuzzleStepCompleted(PuzzleStepSO step) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Only react if we're active and waiting on a puzzle step if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPuzzleStep) @@ -465,19 +469,21 @@ namespace Dialogue // Check if this is the step we're waiting for if (step.stepId == currentNode.puzzleStepID) { - // Instead of immediately moving to the next node, set the flag + // Set the flag that condition is satisfied _conditionSatisfiedPendingAdvance = true; - // Update bubble visibility after state change to show interaction prompt - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } private void OnAnyItemPickedUp(PickupItemData item) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Only react if we're active and waiting on an item pickup if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPickup) @@ -486,14 +492,10 @@ namespace Dialogue // Check if this is the item we're waiting for if (item.itemId == currentNode.pickupItemID) { - // Instead of immediately moving to the next node, set the flag + // Set the flag that condition is satisfied _conditionSatisfiedPendingAdvance = true; - // Update bubble visibility after state change to show interaction prompt - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } @@ -501,6 +503,12 @@ namespace Dialogue { Logging.Debug("[DialogueComponent] OnAnyItemSlotted"); + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Only react if we're active and waiting on a slot if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot) @@ -509,19 +517,21 @@ namespace Dialogue // Check if this is the slot we're waiting for if (slotDefinition.itemId == currentNode.slotItemID) { - // Instead of immediately moving to the next node, set the flag + // Set the flag that condition is satisfied _conditionSatisfiedPendingAdvance = true; - // Update bubble visibility after state change to show interaction prompt - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } private void OnAnyItemsCombined(PickupItemData itemA, PickupItemData itemB, PickupItemData resultItem) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Only react if we're active and waiting on a combination if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnCombination) @@ -530,19 +540,21 @@ namespace Dialogue // Check if this is the result item we're waiting for if (resultItem.itemId == currentNode.combinationResultItemID) { - // Instead of immediately moving to the next node, set the flag + // Set the flag that condition is satisfied _conditionSatisfiedPendingAdvance = true; - // Update bubble visibility after state change to show interaction prompt - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } private void OnAnyIncorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Update the slot state for displaying the correct dialogue lines if (!IsActive || IsCompleted || currentNode == null) return; @@ -554,16 +566,18 @@ namespace Dialogue _currentSlotState = ItemSlotState.Incorrect; _lastSlottedItem = slottedItem; - // Trigger dialogue update - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } private void OnAnyForbiddenItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Update the slot state for displaying the correct dialogue lines if (!IsActive || IsCompleted || currentNode == null) return; @@ -575,16 +589,18 @@ namespace Dialogue _currentSlotState = ItemSlotState.Forbidden; _lastSlottedItem = slottedItem; - // Trigger dialogue update - if (speechBubble != null) - { - speechBubble.UpdatePromptVisibility(HasAnyLines()); - } + UpdateDialogueVisibilityOnItemEvent(); } } private void OnAnyItemSlotCleared(PickupItemData removedItem) { + // Initialize if needed + if (!initialized) + { + StartDialogue(); + } + // Update the slot state when an item is removed if (!IsActive || IsCompleted || currentNode == null) return; @@ -594,8 +610,21 @@ namespace Dialogue { _currentSlotState = ItemSlotState.None; _lastSlottedItem = null; - - // Trigger dialogue update + + UpdateDialogueVisibilityOnItemEvent(); + } + } + + private void UpdateDialogueVisibilityOnItemEvent() + { + // If auto-play is enabled, immediately display dialogue + if (currentNode.shouldAutoPlay) + { + OnCharacterArrived(); + } + else + { + // Manual mode: just update bubble visibility to show interaction prompt if (speechBubble != null) { speechBubble.UpdatePromptVisibility(HasAnyLines()); diff --git a/Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs b/Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs index d08c271b..eaa8651a 100644 --- a/Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs +++ b/Assets/Scripts/Dialogue/RuntimeDialogueGraph.cs @@ -50,6 +50,9 @@ namespace Dialogue public string slotItemID; // For WaitOnSlot public string combinationResultItemID; // For WaitOnCombination + // Auto-play dialogue when condition is met (for item-related nodes) + public bool shouldAutoPlay; + // For WaitOnSlot - different responses [HideInInspector] public List incorrectItemLines = new List(); diff --git a/Assets/Scripts/Interactions/ItemSlot.cs b/Assets/Scripts/Interactions/ItemSlot.cs index 4f179297..3d056486 100644 --- a/Assets/Scripts/Interactions/ItemSlot.cs +++ b/Assets/Scripts/Interactions/ItemSlot.cs @@ -109,6 +109,32 @@ namespace Interactions if ((heldItemData == null && _currentlySlottedItemObject != null) || (heldItemData != null && _currentlySlottedItemObject != null)) { + // If both held and slotted items exist, attempt combination via follower (reuse existing logic from Pickup) + if (heldItemData != null && _currentlySlottedItemData != null) + { + var slottedPickup = _currentlySlottedItemObject?.GetComponent(); + if (slottedPickup != null) + { + var comboResult = FollowerController.TryCombineItems(slottedPickup, out var combinationResultItem); + if (combinationResultItem != null && comboResult == FollowerController.CombinationResult.Successful) + { + // Combination succeeded: fire slot-removed events and clear internals (don't call SlotItem to avoid duplicate events) + onItemSlotRemoved?.Invoke(); + OnItemSlotRemoved?.Invoke(_currentlySlottedItemData); + _currentState = ItemSlotState.None; + + // Clear internal references and visuals + _currentlySlottedItemObject = null; + _currentlySlottedItemData = null; + UpdateSlottedSprite(); + + Interactable.BroadcastInteractionComplete(false); + return; + } + } + } + + // No combination (or not applicable) -> perform normal swap/pickup behavior FollowerController.TryPickupItem(_currentlySlottedItemObject, _currentlySlottedItemData, false); onItemSlotRemoved?.Invoke(); OnItemSlotRemoved?.Invoke(_currentlySlottedItemData); diff --git a/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs b/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs new file mode 100644 index 00000000..09862f3d --- /dev/null +++ b/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs @@ -0,0 +1,40 @@ +using UnityEngine; + +namespace Movement +{ + /// + /// Bridge script that forwards Animation Events from the character art child object + /// to the FollowerController on the parent GameObject. + /// Attach this to the same GameObject that has the Animator component. + /// + public class FollowerAnimationEventBridge : MonoBehaviour + { + [SerializeField] private FollowerController followerController; + + void Awake() + { + // Find the FollowerController on the parent + if (followerController == null) + { + followerController = GetComponentInParent(); + } + + if (followerController == null) + { + Debug.LogError("[FollowerAnimationEventBridge] Could not find FollowerController in parent. This script must be on a child of the FollowerController."); + } + } + + /// + /// Called by Animation Events. Forwards to the parent FollowerController. + /// + public void OnStationaryAnimationComplete() + { + if (followerController != null) + { + followerController.OnStationaryAnimationComplete(); + } + } + } +} + diff --git a/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs.meta b/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs.meta new file mode 100644 index 00000000..61c5991b --- /dev/null +++ b/Assets/Scripts/Movement/FollowerAnimationEventBridge.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 39f2e64420884f11a9022fea7b3be5d0 +timeCreated: 1761571513 \ No newline at end of file diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs index dc4b0238..7eaaceed 100644 --- a/Assets/Scripts/Movement/FollowerController.cs +++ b/Assets/Scripts/Movement/FollowerController.cs @@ -11,6 +11,8 @@ using Core; /// public class FollowerController: MonoBehaviour { + private static readonly int CombineTrigger = Animator.StringToHash("Combine"); + [Header("Follower Settings")] public bool debugDrawTarget = true; /// @@ -52,6 +54,11 @@ public class FollowerController: MonoBehaviour /// public SpriteRenderer heldObjectRenderer; + // Stationary animation state + private bool _isPlayingStationaryAnimation = false; + private Coroutine _stationaryAnimationCoroutine; + private System.Action _stationaryAnimationCallback; + private bool _isReturningToPlayer = false; private float _playerMaxSpeed = 5f; private float _followerMaxSpeed = 6f; @@ -117,6 +124,19 @@ public class FollowerController: MonoBehaviour return; } + // Skip all movement logic when playing a stationary animation + if (_isPlayingStationaryAnimation) + { + // Still update animator with zero speed to maintain idle state + if (_animator != null) + { + _animator.SetFloat("Speed", 0f); + _animator.SetFloat("DirX", _lastDirX); + _animator.SetFloat("DirY", _lastDirY); + } + return; + } + _timer += Time.deltaTime; if (_timer >= _settings.FollowUpdateInterval) { @@ -420,6 +440,100 @@ public class FollowerController: MonoBehaviour } #endregion Movement +#region StationaryAnimations + /// + /// Pauses all movement. Can be called directly from Timeline, Animation Events, or code. + /// Call ResumeMovement() to resume. + /// + public void PauseMovement() + { + _isPlayingStationaryAnimation = true; + _currentSpeed = 0f; + Logging.Debug("[FollowerController] Movement paused"); + } + + /// + /// Resumes movement after being paused. Can be called from Timeline, Animation Events, or code. + /// + public void ResumeMovement() + { + _isPlayingStationaryAnimation = false; + Logging.Debug("[FollowerController] Movement resumed"); + } + + /// + /// Plays an animation while pausing all movement. Resumes movement when animation completes. + /// Uses a hybrid approach: Animation Events (if set up) OR timeout fallback (if not). + /// To use Animation Events: Add an event at the end of your animation that calls OnStationaryAnimationComplete(). + /// + /// The animator trigger name to activate + /// Maximum time to wait before auto-resuming (fallback if no Animation Event) + /// Optional callback to invoke when animation completes + public void PlayAnimationStationary(string triggerName, float maxDuration = 2f, System.Action onComplete = null) + { + if (_animator == null) + { + Logging.Warning("[FollowerController] Cannot play stationary animation - no Animator found"); + onComplete?.Invoke(); + return; + } + + // Stop any existing stationary animation + if (_stationaryAnimationCoroutine != null) + { + StopCoroutine(_stationaryAnimationCoroutine); + } + + _isPlayingStationaryAnimation = true; + _stationaryAnimationCallback = onComplete; + _currentSpeed = 0f; // Immediately stop movement + + // Trigger the animation + _animator.SetTrigger(triggerName); + + // Start timeout coroutine (will be stopped early if Animation Event fires) + _stationaryAnimationCoroutine = StartCoroutine(StationaryAnimationTimeoutCoroutine(maxDuration)); + } + + private System.Collections.IEnumerator StationaryAnimationTimeoutCoroutine(float maxDuration) + { + yield return new WaitForSeconds(maxDuration); + + // If we reach here, the Animation Event didn't fire - use fallback + Logging.Debug($"[FollowerController] Stationary animation timeout reached ({maxDuration}s) - resuming movement"); + ResumeMovementAfterAnimation(); + } + + /// + /// Public method to be called by Animation Events at the end of stationary animations. + /// Add this as an Animation Event in your animation clip for frame-perfect timing. + /// + public void OnStationaryAnimationComplete() + { + Logging.Debug("[FollowerController] Stationary animation completed via Animation Event"); + ResumeMovementAfterAnimation(); + } + + private void ResumeMovementAfterAnimation() + { + if (!_isPlayingStationaryAnimation) return; // Already resumed + + // Stop the timeout coroutine if it's still running + if (_stationaryAnimationCoroutine != null) + { + StopCoroutine(_stationaryAnimationCoroutine); + _stationaryAnimationCoroutine = null; + } + + _isPlayingStationaryAnimation = false; + + // Invoke callback if provided + var callback = _stationaryAnimationCallback; + _stationaryAnimationCallback = null; + callback?.Invoke(); + } +#endregion StationaryAnimations + #region ItemInteractions public void TryPickupItem(GameObject itemObject, PickupItemData itemData, bool dropItem = true) { @@ -445,7 +559,7 @@ public class FollowerController: MonoBehaviour public CombinationResult TryCombineItems(Pickup pickupA, out GameObject newItem) { - _animator.ResetTrigger("Combine"); + _animator.ResetTrigger(CombineTrigger); newItem = null; if (_cachedPickupObject == null) { @@ -468,7 +582,7 @@ public class FollowerController: MonoBehaviour Destroy(pickupA.gameObject); Destroy(pickupB.gameObject); TryPickupItem(newItem, itemData); - _animator.SetTrigger("Combine"); + PlayAnimationStationary("Combine", 10.0f); return CombinationResult.Successful; } diff --git a/Assets/Scripts/Tests/CardSystemTester.cs b/Assets/Scripts/Tests/CardSystemTester.cs index fbcdc89b..fc98aafc 100644 --- a/Assets/Scripts/Tests/CardSystemTester.cs +++ b/Assets/Scripts/Tests/CardSystemTester.cs @@ -177,7 +177,7 @@ namespace AppleHills.Tests #if UNITY_EDITOR [CustomEditor(typeof(CardSystemTester))] - public class CardSystemTesterEditor : Editor + public class CardSystemTesterEditor : UnityEditor.Editor { public override void OnInspectorGUI() { diff --git a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs index f325c744..aa0452ab 100644 --- a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs +++ b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs @@ -274,7 +274,7 @@ namespace UI.CardSystem if (canvasGroup != null) { canvasGroup.alpha = 0f; - Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { @@ -288,7 +288,7 @@ namespace UI.CardSystem // Simple fade out animation if (canvasGroup != null) { - Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { diff --git a/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs b/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs index 81a1142c..9fba0a38 100644 --- a/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs +++ b/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs @@ -177,7 +177,8 @@ namespace UI.CardSystem pulseDuration/2, 0, Tween.EaseIn); - }); + }, + obeyTimescale: false); } } } diff --git a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs index 09b5a892..b340a8c4 100644 --- a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs +++ b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs @@ -54,7 +54,7 @@ namespace UI.CardSystem private void Awake() { _cardManager = CardSystemManager.Instance; - _cardAlbumUI = FindObjectOfType(); + _cardAlbumUI = FindFirstObjectByType(); // Set up button listeners if (openBoosterButton != null) @@ -255,7 +255,7 @@ namespace UI.CardSystem // Animate the booster pack opening Tween.LocalScale(boosterPackObject.transform, Vector3.zero, 0.3f, 0f, Tween.EaseInBack, Tween.LoopType.None, null, () => { boosterPackObject.SetActive(false); - }); + }, obeyTimescale: false); } if (openBoosterButton != null) @@ -322,7 +322,7 @@ namespace UI.CardSystem Debug.Log($"[BoosterOpeningPage] Card back {i} activated"); // Play reveal animation using Pixelplacement.Tween - Tween.LocalScale(cardBackObj.transform, Vector3.one, 0.5f, 0f, Tween.EaseOutBack); + Tween.LocalScale(cardBackObj.transform, Vector3.one, 0.5f, 0f, Tween.EaseOutBack, obeyTimescale: false); // Wait for animation delay yield return new WaitForSeconds(cardRevealDelay); @@ -374,7 +374,7 @@ namespace UI.CardSystem Transform cardBackTransform = cardBack.transform; // Step 1: Flip the card 90 degrees (showing the edge) - Tween.LocalRotation(cardBackTransform, new Vector3(0, 90, 0), flipAnimationDuration * 0.5f, 0); + Tween.LocalRotation(cardBackTransform, new Vector3(0, 90, 0), flipAnimationDuration * 0.5f, 0, obeyTimescale: false); // Wait for half the flip duration yield return new WaitForSeconds(flipAnimationDuration * 0.5f); @@ -405,7 +405,7 @@ namespace UI.CardSystem } // Step 3: Finish the flip animation (from 90 degrees to 0) - Tween.LocalRotation(cardObj.transform, Vector3.zero, flipAnimationDuration * 0.5f, 0); + Tween.LocalRotation(cardObj.transform, Vector3.zero, flipAnimationDuration * 0.5f, 0, obeyTimescale: false); // Increment counter of revealed cards _revealedCardCount++; @@ -441,8 +441,8 @@ namespace UI.CardSystem Vector3 originalScale = cardTransform.localScale; // Sequence: Scale up slightly, then back to normal - Tween.LocalScale(cardTransform, originalScale * 1.2f, 0.2f, 0.1f, Tween.EaseOutBack); - Tween.LocalScale(cardTransform, originalScale, 0.15f, 0.3f, Tween.EaseIn); + Tween.LocalScale(cardTransform, originalScale * 1.2f, 0.2f, 0.1f, Tween.EaseOutBack, obeyTimescale: false); + Tween.LocalScale(cardTransform, originalScale, 0.15f, 0.3f, Tween.EaseIn, obeyTimescale: false); // Play sound effect based on rarity (if available) // This would require audio source components to be set up @@ -463,7 +463,7 @@ namespace UI.CardSystem continueButton.gameObject.SetActive(true); continueButton.transform.localScale = Vector3.zero; - Tween.LocalScale(continueButton.transform, Vector3.one, 0.3f, 0f, Tween.EaseOutBack); + Tween.LocalScale(continueButton.transform, Vector3.one, 0.3f, 0f, Tween.EaseOutBack, obeyTimescale: false); } } @@ -542,8 +542,8 @@ namespace UI.CardSystem Vector3 targetPos = card.transform.parent.InverseTransformPoint(backpackWorldPos); // Start the move animation - ensure no cancellation between animations - Tween.LocalPosition(card.transform, targetPos, animationDuration, cardDelay * i, Tween.EaseInOut); - Tween.LocalScale(card.transform, Vector3.zero, animationDuration, cardDelay * i, Tween.EaseIn); + Tween.LocalPosition(card.transform, targetPos, animationDuration, cardDelay * i, Tween.EaseInOut, obeyTimescale: false); + Tween.LocalScale(card.transform, Vector3.zero, animationDuration, cardDelay * i, Tween.EaseIn, obeyTimescale: false); Debug.Log($"[BoosterOpeningPage] Starting animation for card {i}"); } @@ -578,7 +578,7 @@ namespace UI.CardSystem if (canvasGroup != null) { canvasGroup.alpha = 0f; - Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { @@ -595,7 +595,7 @@ namespace UI.CardSystem // Simple fade out animation if (canvasGroup != null) { - Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { diff --git a/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs b/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs index 3bcafcc4..f15a6a4e 100644 --- a/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs +++ b/Assets/Scripts/UI/CardSystem/CardAlbumUI.cs @@ -268,7 +268,7 @@ namespace UI.CardSystem // Animate the notification dot for feedback boosterNotificationDot.transform.localScale = Vector3.one * 1.2f; - Tween.LocalScale(boosterNotificationDot.transform, Vector3.one, 0.3f, 0f); + Tween.LocalScale(boosterNotificationDot.transform, Vector3.one, 0.3f, 0f, obeyTimescale: false); // Update visibility based on count UpdateBoosterVisibility(); diff --git a/Assets/Scripts/UI/CardSystem/CardMenuPage.cs b/Assets/Scripts/UI/CardSystem/CardMenuPage.cs index 7b9edaa8..efeaa416 100644 --- a/Assets/Scripts/UI/CardSystem/CardMenuPage.cs +++ b/Assets/Scripts/UI/CardSystem/CardMenuPage.cs @@ -175,7 +175,7 @@ namespace UI.CardSystem if (canvasGroup != null) { canvasGroup.alpha = 0f; - Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(0f, 1f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { @@ -192,7 +192,7 @@ namespace UI.CardSystem // Simple fade out animation if (canvasGroup != null) { - Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete); + Tween.Value(canvasGroup.alpha, 0f, (value) => canvasGroup.alpha = value, transitionDuration, 0f, Tween.EaseInOut, Tween.LoopType.None, null, onComplete, obeyTimescale: false); } else { diff --git a/Assets/Scripts/UI/CardSystem/CardUIElement.cs b/Assets/Scripts/UI/CardSystem/CardUIElement.cs index 5de15881..fe764588 100644 --- a/Assets/Scripts/UI/CardSystem/CardUIElement.cs +++ b/Assets/Scripts/UI/CardSystem/CardUIElement.cs @@ -233,7 +233,7 @@ namespace UI.CardSystem #if UNITY_EDITOR [CustomEditor(typeof(CardUIElement))] - public class CardUIElementEditor : Editor + public class CardUIElementEditor : UnityEditor.Editor { public override void OnInspectorGUI() { diff --git a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs index 56b4e1e7..21731d60 100644 --- a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs +++ b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs @@ -1,6 +1,7 @@ using System.Collections; using Bootstrap; using Core; +using Core.SaveLoad; using Input; using Pixelplacement; using UI.Core; @@ -10,8 +11,15 @@ namespace UI.Tutorial { public class DivingTutorial : MonoBehaviour, ITouchInputConsumer { + public enum ProgressType + { + Manual, // Wait for player tap after animation loop + Auto // Automatically progress after animation loop + } + private StateMachine _stateMachine; public bool playTutorial; + [SerializeField] private ProgressType progressType = ProgressType.Auto; // gating for input until current state's animation finishes first loop [SerializeField] private GameObject tapPrompt; @@ -31,8 +39,10 @@ namespace UI.Tutorial void InitializeTutorial() { - if (playTutorial) + if (playTutorial && !SaveLoadManager.Instance.currentSaveData.playedDivingTutorial) { + // TODO: Possibly do it better, but for now just mark tutorial as played immediately + SaveLoadManager.Instance.currentSaveData.playedDivingTutorial = true; // pause the game, hide UI, and register for input overrides GameManager.Instance.RequestPause(this); UIPageController.Instance.HideAllUI(); @@ -104,7 +114,8 @@ namespace UI.Tutorial _canAcceptInput = allow; if (tapPrompt != null) { - tapPrompt.SetActive(allow); + // Only show tap prompt in Manual mode + tapPrompt.SetActive(allow && progressType == ProgressType.Manual); } } @@ -198,7 +209,19 @@ namespace UI.Tutorial yield return null; } - SetInputEnabled(true); + // After first loop completes, handle based on progress type + if (progressType == ProgressType.Auto) + { + // Auto mode: immediately progress to next state + _stateMachine.Next(true); + SetupInputGateForCurrentState(); + } + else + { + // Manual mode: enable input and wait for player tap + SetInputEnabled(true); + } + _waitLoopCoroutine = null; } } diff --git a/Assets/Settings/Developer/DebugSettings.asset b/Assets/Settings/Developer/DebugSettings.asset index 483f1b91..19aa723c 100644 --- a/Assets/Settings/Developer/DebugSettings.asset +++ b/Assets/Settings/Developer/DebugSettings.asset @@ -13,3 +13,4 @@ MonoBehaviour: m_Name: DebugSettings m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Core.Settings.DebugSettings showDebugUiMessages: 1 + pauseTimeOnPauseGame: 1 diff --git a/Assets/Signals.meta b/Assets/Signals.meta new file mode 100644 index 00000000..ad453ee6 --- /dev/null +++ b/Assets/Signals.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2ba6a6b0f020a9741a46f32cdfe270f3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Signals/PausePulverMovement.signal b/Assets/Signals/PausePulverMovement.signal new file mode 100644 index 00000000..00daf8a0 --- /dev/null +++ b/Assets/Signals/PausePulverMovement.signal @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d6fa2d92fc1b3f34da284357edf89c3b, type: 3} + m_Name: PausePulverMovement + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalAsset diff --git a/Assets/Signals/PausePulverMovement.signal.meta b/Assets/Signals/PausePulverMovement.signal.meta new file mode 100644 index 00000000..e8e31a1b --- /dev/null +++ b/Assets/Signals/PausePulverMovement.signal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2a32e2abdb44cb1469e5c6b9a25f98bc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Signals/ResumePulverMovement.signal b/Assets/Signals/ResumePulverMovement.signal new file mode 100644 index 00000000..f801223c --- /dev/null +++ b/Assets/Signals/ResumePulverMovement.signal @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d6fa2d92fc1b3f34da284357edf89c3b, type: 3} + m_Name: ResumePulverMovement + m_EditorClassIdentifier: Unity.Timeline::UnityEngine.Timeline.SignalAsset diff --git a/Assets/Signals/ResumePulverMovement.signal.meta b/Assets/Signals/ResumePulverMovement.signal.meta new file mode 100644 index 00000000..f8f94211 --- /dev/null +++ b/Assets/Signals/ResumePulverMovement.signal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2ec36cde76b6baf4299c0dd9ab54714e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: