diff --git a/ReadMe.md b/ReadMe.md index cb9eb1da..6be2d184 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,3 +1,24 @@ +# Modded from the original timespinner randomizer + +This Modded Tsrandomizer has an extra options menu for a few Quality of life settings: + +-Auto Skip Cutscenes and dialogue instantly + +-Change item stack cap (9, 25, 50, 99) + +-Toast Popup Speed options (Fast, Vanilla) + +-Toasts Block Movement (On, Off) + +stack cap is intended to make other players in large multiworlds feel like their 70th Berry Pick-mi up matters. + +this QoL Mod is intended to make gameplay smoother for players who know Timespinner by heart and don't want control taken away from them every time they obtain an orb #LetMePlayDamnIt + +You can find the menu in the options menu + +image + + # General Timespinner Randomizer will randomize the location of items such as equipment, relics, familiars, stat boosts, use items. The logic makes sure that each game you play is beatable. diff --git a/TsRandomizer.ItemTracker/TsRandomizer.ItemTracker.csproj b/TsRandomizer.ItemTracker/TsRandomizer.ItemTracker.csproj index 6981e697..5433c113 100644 --- a/TsRandomizer.ItemTracker/TsRandomizer.ItemTracker.csproj +++ b/TsRandomizer.ItemTracker/TsRandomizer.ItemTracker.csproj @@ -1,134 +1,129 @@ - - - - - Debug - AnyCPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101} - WinExe - TsRandomizerItemTracker - TsRandomizerItemTracker - v4.8 - 512 - true - - - - x86 - true - full - false - ..\..\TestVersions\DRMFreeVersion\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ - TRACE - prompt - 4 - - - true - - - - - - EyeOrbLargeT.ico - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - - - true - bin\Debug DRM Free %28GoG%29\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - - - true - bin\x64\Debug DRM Free %28GoG%29\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - MinimumRecommendedRules.ruleset - - - bin\Release DRM Free %28GoG%29\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - - - bin\x64\Release DRM Free %28GoG%29\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - MinimumRecommendedRules.ruleset - - - - False - C:\Program Files (x86)\Steam\steamapps\common\Timespinner\FNA.dll - False - - - - - - - False - C:\Program Files (x86)\Steam\steamapps\common\Timespinner\Timespinner.exe - False - - - False - ..\..\TestVersions\SteamVerison\TsRandomizer.exe - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101} + WinExe + TsRandomizerItemTracker + TsRandomizerItemTracker + v4.8 + 512 + true + + + + x86 + true + full + false + ..\..\TestVersions\DRMFreeVersion\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ + TRACE + prompt + 4 + + + true + + + + + + EyeOrbLargeT.ico + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\Debug DRM Free %28GoG%29\ + DEBUG;TRACE + full + x86 + 7.3 + prompt + + + true + bin\x64\Debug DRM Free %28GoG%29\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + bin\Release DRM Free %28GoG%29\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + + + bin\x64\Release DRM Free %28GoG%29\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + + ..\..\TestVersions\DebugDRMFreeGoG\FNA.dll + + + + + + + ..\..\TestVersions\DebugDRMFreeGoG\Timespinner.exe + + + ..\..\TestVersions\DebugDRMFreeGoG\TsRandomizer.exe + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TsRandomizer.SeedGeneratah/TsRandomizer.SeedGeneratah.csproj b/TsRandomizer.SeedGeneratah/TsRandomizer.SeedGeneratah.csproj index 91c94d1e..fb586415 100644 --- a/TsRandomizer.SeedGeneratah/TsRandomizer.SeedGeneratah.csproj +++ b/TsRandomizer.SeedGeneratah/TsRandomizer.SeedGeneratah.csproj @@ -1,75 +1,71 @@ - - - - - Debug - AnyCPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8} - Exe - TsRandomizerSeedGeneratah - TsRandomizerSeedGeneratah - v4.0 - 512 - true - Client - - - x86 - true - full - false - C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ - TRACE - prompt - 4 - - - true - bin\Debug DRM Free %28GoG%29\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - - - bin\Release DRM Free %28GoG%29\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - - - - - False - ..\..\TestVersions\SteamVersion\Timespinner.exe - False - - - False - ..\..\TestVersions\SteamVerison\TsRandomizer.exe - False - - - - - - - - - - - + + + + + Debug + AnyCPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8} + Exe + TsRandomizerSeedGeneratah + TsRandomizerSeedGeneratah + v4.0 + 512 + true + Client + + + x86 + true + full + false + C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ + TRACE + prompt + 4 + + + true + bin\Debug DRM Free %28GoG%29\ + DEBUG;TRACE + full + x86 + 7.3 + prompt + + + bin\Release DRM Free %28GoG%29\ + TRACE + true + pdbonly + x86 + 7.3 + prompt + + + + + ..\..\TestVersions\DebugDRMFreeGoG\Timespinner.exe + + + ..\..\TestVersions\DebugDRMFreeGoG\TsRandomizer.exe + + + + + + + + + + + \ No newline at end of file diff --git a/TsRandomizer.sln b/TsRandomizer.sln index e99f34b5..0471c57a 100644 --- a/TsRandomizer.sln +++ b/TsRandomizer.sln @@ -1,118 +1,112 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31829.152 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer", "TsRandomizer\TsRandomizer.csproj", "{694E46C5-FD46-4CC8-8B71-D381E154CCF7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD8103A2-141D-4AD0-B2FE-533D86875C9C}" - ProjectSection(SolutionItems) = preProject - ReadMe.md = ReadMe.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer.ItemTracker", "TsRandomizer.ItemTracker\TsRandomizer.ItemTracker.csproj", "{4D86B28B-95A2-4452-9BAA-C8E80F026101}" - ProjectSection(ProjectDependencies) = postProject - {694E46C5-FD46-4CC8-8B71-D381E154CCF7} = {694E46C5-FD46-4CC8-8B71-D381E154CCF7} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer.SeedGeneratah", "TsRandomizer.SeedGeneratah\TsRandomizer.SeedGeneratah.csproj", "{C30E617E-83DB-4C7C-9E45-0166C781A0A8}" - ProjectSection(ProjectDependencies) = postProject - {694E46C5-FD46-4CC8-8B71-D381E154CCF7} = {694E46C5-FD46-4CC8-8B71-D381E154CCF7} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug DRM Free (GoG)|Any CPU = Debug DRM Free (GoG)|Any CPU - Debug DRM Free (GoG)|x64 = Debug DRM Free (GoG)|x64 - Debug DRM Free (Humble)|Any CPU = Debug DRM Free (Humble)|Any CPU - Debug DRM Free (Humble)|x64 = Debug DRM Free (Humble)|x64 - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release DRM Free (GoG)|Any CPU = Release DRM Free (GoG)|Any CPU - Release DRM Free (GoG)|x64 = Release DRM Free (GoG)|x64 - Release DRM Free (Humble)|Any CPU = Release DRM Free (Humble)|Any CPU - Release DRM Free (Humble)|x64 = Release DRM Free (Humble)|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|x64.Build.0 = Debug DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|x64.ActiveCfg = Debug|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|x64.Build.0 = Debug|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|Any CPU.Build.0 = Release|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|x64.ActiveCfg = Release DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|x64.Build.0 = Release DRM Free|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|Any CPU.Build.0 = Release|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|x64.ActiveCfg = Release|Any CPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|x64.Build.0 = Release|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|x64.Build.0 = Debug|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|x64.ActiveCfg = Debug|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|x64.Build.0 = Debug|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|Any CPU.Build.0 = Release|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|x64.ActiveCfg = Release|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|x64.Build.0 = Release|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|Any CPU.Build.0 = Release|Any CPU - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|x64.ActiveCfg = Release|x64 - {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|x64.Build.0 = Release|x64 - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|x64.Build.0 = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|x64.ActiveCfg = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|x64.Build.0 = Debug|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|Any CPU.Build.0 = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|x64.ActiveCfg = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|x64.Build.0 = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|Any CPU.Build.0 = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|x64.ActiveCfg = Release|Any CPU - {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5C37E60C-1A8D-4AE6-812F-A9C3301D87B9} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31829.152 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer", "TsRandomizer\TsRandomizer.csproj", "{694E46C5-FD46-4CC8-8B71-D381E154CCF7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD8103A2-141D-4AD0-B2FE-533D86875C9C}" + ProjectSection(SolutionItems) = preProject + ReadMe.md = ReadMe.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer.ItemTracker", "TsRandomizer.ItemTracker\TsRandomizer.ItemTracker.csproj", "{4D86B28B-95A2-4452-9BAA-C8E80F026101}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TsRandomizer.SeedGeneratah", "TsRandomizer.SeedGeneratah\TsRandomizer.SeedGeneratah.csproj", "{C30E617E-83DB-4C7C-9E45-0166C781A0A8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug DRM Free (GoG)|Any CPU = Debug DRM Free (GoG)|Any CPU + Debug DRM Free (GoG)|x64 = Debug DRM Free (GoG)|x64 + Debug DRM Free (Humble)|Any CPU = Debug DRM Free (Humble)|Any CPU + Debug DRM Free (Humble)|x64 = Debug DRM Free (Humble)|x64 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release DRM Free (GoG)|Any CPU = Release DRM Free (GoG)|Any CPU + Release DRM Free (GoG)|x64 = Release DRM Free (GoG)|x64 + Release DRM Free (Humble)|Any CPU = Release DRM Free (Humble)|Any CPU + Release DRM Free (Humble)|x64 = Release DRM Free (Humble)|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug DRM Free (Humble)|x64.Build.0 = Debug DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|x64.ActiveCfg = Debug|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Debug|x64.Build.0 = Debug|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|Any CPU.Build.0 = Release|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|x64.ActiveCfg = Release DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release DRM Free (Humble)|x64.Build.0 = Release DRM Free|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|Any CPU.Build.0 = Release|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|x64.ActiveCfg = Release|Any CPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7}.Release|x64.Build.0 = Release|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug DRM Free (Humble)|x64.Build.0 = Debug DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|x64.ActiveCfg = Debug|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Debug|x64.Build.0 = Debug|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|x64.ActiveCfg = Release DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release DRM Free (Humble)|x64.Build.0 = Release DRM Free (GoG)|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|Any CPU.Build.0 = Release|Any CPU + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|x64.ActiveCfg = Release|x64 + {4D86B28B-95A2-4452-9BAA-C8E80F026101}.Release|x64.Build.0 = Release|x64 + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|x64.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (GoG)|x64.Build.0 = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|Any CPU.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|Any CPU.Build.0 = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|x64.ActiveCfg = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug DRM Free (Humble)|x64.Build.0 = Debug DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Debug|x64.Build.0 = Debug|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|x64.ActiveCfg = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (GoG)|x64.Build.0 = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|Any CPU.ActiveCfg = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|Any CPU.Build.0 = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|x64.ActiveCfg = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release DRM Free (Humble)|x64.Build.0 = Release DRM Free (GoG)|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|Any CPU.Build.0 = Release|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|x64.ActiveCfg = Release|Any CPU + {C30E617E-83DB-4C7C-9E45-0166C781A0A8}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5C37E60C-1A8D-4AE6-812F-A9C3301D87B9} + EndGlobalSection +EndGlobal diff --git a/TsRandomizer/LevelObjects/ItemManipulator.cs b/TsRandomizer/LevelObjects/ItemManipulator.cs index 4792976b..6c9e92d1 100644 --- a/TsRandomizer/LevelObjects/ItemManipulator.cs +++ b/TsRandomizer/LevelObjects/ItemManipulator.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using Timespinner.GameAbstractions.Gameplay; +using Timespinner.GameAbstractions.Inventory; +using Timespinner.GameAbstractions.Saving; using Timespinner.GameObjects.BaseClasses; using TsRandomizer.Extensions; using TsRandomizer.IntermediateObjects; @@ -32,7 +34,7 @@ abstract class ItemManipulator : LevelObject public readonly ItemInfo ItemInfo; public readonly ItemLocation ItemLocation; - protected ItemManipulator(Mobile typedObject, GameplayScreen gameplayScreen, ItemLocation itemLocation) + protected ItemManipulator(Mobile typedObject, GameplayScreen gameplayScreen, ItemLocation itemLocation) : base(typedObject, gameplayScreen) { ItemInfo = itemLocation?.ItemInfo; @@ -46,6 +48,8 @@ protected void AwardContainedItem() if (ItemInfo.Identifier.LootType == LootType.ConstRelic) LevelReflected.UnlockRelic(ItemInfo.Identifier.Relic); + ApplyStackCap(Level.GameSave); + OnItemPickup(); } @@ -60,6 +64,27 @@ protected void OnItemPickup() public static void Initialize(ItemLocationMap itemLocations) => itemLocationMap = itemLocations; + public static void ApplyStackCap(GameSave save) + { + int cap = QoLSettings.Current.StackCap; + var inventory = save.Inventory.UseItemInventory.Inventory; + + foreach (var kvp in inventory) + { + var item = kvp.Value; + + // Leave special currency items at vanilla cap + var type = (EInventoryUseItemType)kvp.Key; + if (type == EInventoryUseItemType.MagicMarbles + || type == EInventoryUseItemType.EssenceCrystal + || type == EInventoryUseItemType.GoldRing + || type == EInventoryUseItemType.GoldNecklace) + continue; + + item.StackCap = cap; + } + } + public static ItemManipulator GenerateShadowObject( Type levelObjectType, Mobile obj, GameplayScreen gameplayScreen, ItemLocationMap itemLocations) { @@ -67,8 +92,8 @@ public static ItemManipulator GenerateShadowObject( var itemLocation = itemLocations[itemKey]; if (itemLocation == null) - { - //Console.Out.WriteLine($"UnmappedItem: {itemKey}"); + { + //Console.Out.WriteLine($"UnmappedItem: {itemKey}"); return null; } @@ -80,7 +105,7 @@ protected void ShowItemAwardPopup() if (ItemInfo is CustomItem customItem) GameplayScreen.ShowItemPickupBar(customItem.Name); else if (ItemInfo is ProgressiveItemInfo progressiveItem) - Level.ShowItemAwardPopup(progressiveItem.PreviousItem.Identifier); //since this script runs delayed, the item will already have updated + Level.ShowItemAwardPopup(progressiveItem.PreviousItem.Identifier); //since this script runs delayed, the item will already have updated else Level.ShowItemAwardPopup(ItemInfo.Identifier); } @@ -100,4 +125,4 @@ protected void UpdateRelicOrbGetToastToItem() reflectedScript.Arguments = ScriptActionQueueExtensions.ReplacedArguments; } } -} +} \ No newline at end of file diff --git a/TsRandomizer/Program.cs b/TsRandomizer/Program.cs index 759cf633..8133a402 100644 --- a/TsRandomizer/Program.cs +++ b/TsRandomizer/Program.cs @@ -1,36 +1,39 @@ -using System; - -namespace TsRandomizer -{ - public static class Program - { - [STAThread] - public static int Main() - { - WithExceptionLogging(() => { - var platformHelper = DummyPlatformHelper.CreateInstance(); - - new TimeSpinnerGame(platformHelper).Run(); - }); - - Environment.Exit(0); - return 0; - } - - static void WithExceptionLogging(Action action) - { -#if DEBUG - action(); -#else - try - { - action(); - } - catch (Exception e) - { - ExceptionLogger.LogException(e); - } -#endif - } - } -} +using System; + + +namespace TsRandomizer +{ + public static class Program + { + [STAThread] + public static int Main() + { + WithExceptionLogging(() => { + + + var platformHelper = DummyPlatformHelper.CreateInstance(); + + new TimeSpinnerGame(platformHelper).Run(); + }); + + Environment.Exit(0); + return 0; + } + + static void WithExceptionLogging(Action action) + { +#if DEBUG + action(); +#else + try + { + action(); + } + catch (Exception e) + { + ExceptionLogger.LogException(e); + } +#endif + } + } +} diff --git a/TsRandomizer/Screens/GameSettingsScreen.cs b/TsRandomizer/Screens/GameSettingsScreen.cs index 0ce2ecc5..ebc05844 100644 --- a/TsRandomizer/Screens/GameSettingsScreen.cs +++ b/TsRandomizer/Screens/GameSettingsScreen.cs @@ -14,8 +14,8 @@ namespace TsRandomizer.Screens { - [TimeSpinnerType("Timespinner.GameStateManagement.Screens.PauseMenu.JournalMenuScreen")] - // ReSharper disable once UnusedMember.Global + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.PauseMenu.JournalMenuScreen")] + // ReSharper disable once UnusedMember.Global class GameSettingsScreen : Screen { static readonly Type MenuEntryType = @@ -25,6 +25,7 @@ class GameSettingsScreen : Screen readonly SeedSelectionMenuScreen seedSelectionScreen; readonly OptionsMenuScreen optionsMenuScreen; + readonly bool isQoLMenu; GameSave save; SettingCollection settings; @@ -37,6 +38,12 @@ public override void Initialize(ItemLocationMap itemLocationMap, GCM gameContent { gcm = gameContentManager; + if (isQoLMenu) + { + QoLSettingsMenu.BuildMenu(GameScreen, 0); + return; + } + var gameplayScreen = ScreenManager.FirstOrDefault(); save = gameplayScreen?.Save; @@ -49,9 +56,9 @@ public override void Initialize(ItemLocationMap itemLocationMap, GCM gameContent Dynamic._menuTitle = "Randomizer Settings"; ResetMenu(); } - else - { - // Journal is being used as a journal, replace Feats and Quests with Objectives and Statistics + else + { + // Journal is being used as a journal, replace Feats and Quests with Objectives and Statistics SetStatistics(itemLocationMap); SetObjectives(); } @@ -66,8 +73,8 @@ public override void Unload() gcm.UpdateMinimapColors(settings); if (IsInGame) - { - var gameplayScreen = ScreenManager.FirstOrDefault(); + { + var gameplayScreen = ScreenManager.FirstOrDefault(); if (gameplayScreen != null) SpriteManager.ReloadCustomSprites(gameplayScreen.Level, gcm, settings); } @@ -78,8 +85,8 @@ public override void Unload() } } catch - { - // ignored + { + // ignored } } @@ -87,6 +94,8 @@ public GameSettingsScreen(ScreenManager screenManager, GameScreen passwordMenuSc { seedSelectionScreen = screenManager.FirstOrDefault(); optionsMenuScreen = screenManager.FirstOrDefault(); + isQoLMenu = QoLSettingsMenu.IsQoLMenuPending; + QoLSettingsMenu.IsQoLMenuPending = false; } public static GameScreen Create(ScreenManager screenManager) @@ -121,8 +130,8 @@ object CreateDefaultsMenu(GameSettingCategoryInfo[] menusToClear, bool isSubmenu } void OnDefaultsSelected(GameSettingCategoryInfo[] menusToClear, bool isSubmenu) - { - // Clear the root menu + { + // Clear the root menu if (!IsInGame && !isSubmenu) { settings = new SettingCollection(); @@ -135,7 +144,7 @@ void OnDefaultsSelected(GameSettingCategoryInfo[] menusToClear, bool isSubmenu) { var setting = settingsFunc(settings); - if(IsInGame && !setting.CanBeChangedInGame) + if (IsInGame && !setting.CanBeChangedInGame) continue; setting.SetDefault(); @@ -198,7 +207,7 @@ void CreateMenuForCategory(GameSettingCategoryInfo category) foreach (var settingFunc in category.SettingsPerCategory) submenu.Add(CreateMenuForSetting(settingFunc(settings)).AsTimeSpinnerMenuEntry()); - submenu.Add(CreateDefaultsMenu(new [] { category }, true)); + submenu.Add(CreateDefaultsMenu(new[] { category }, true)); Dynamic.ChangeMenuCollection(collection, true); ((object)Dynamic._selectedMenuCollection).AsDynamic().SetSelectedIndex(0); @@ -206,20 +215,22 @@ void CreateMenuForCategory(GameSettingCategoryInfo category) object FetchCollection(string submenu) { - dynamic collection; - // Multiple submenus can share the same inventory collection - // as the in-use collection is cleared before use. + dynamic collection; + // Multiple submenus can share the same inventory collection + // as the in-use collection is cleared before use. + switch (submenu) - { - // Currently using quest layout for most, other layouts may be useful for other menus - // Leaving as switch to easily add new menus as Memories, Letters, Files, Quests, Bestiary, Feats + { + // Currently using quest layout for most, other layouts may be useful for other menus + // Leaving as switch to easily add new menus as Memories, Letters, Files, Quests, Bestiary, Feats /* // TODO: Sprites needs a functional way to write a FeatsMenuEntry before we can properly preview sprites // and use this layout case "Sprites": collection = Dynamic._featsInventory; break; - */ + */ + default: collection = ((object)Dynamic._questInventory).AsDynamic(); break; @@ -266,6 +277,7 @@ void ToggleSetting(GameSetting setting) setting.UpdateMenuEntry(menuEntry); } + void SetStatistics(ItemLocationMap itemLocationMap) { var gameplayScreen = ScreenManager.FirstOrDefault(); @@ -303,14 +315,14 @@ void SetObjectives() menuEntry.AsDynamic().Text = "Goal: Dad Percent"; menuEntry.AsDynamic().Description = "Clear the boss fight at the top of Emperor's Tower."; } - menuEntry.AsDynamic()._isUnlocked = save.GetSaveBool("TsRandoGoalCleared"); - - // Remove all extra feat entries - // Currently bonus objectives are not available, only goals + menuEntry.AsDynamic()._isUnlocked = save.GetSaveBool("TsRandoGoalCleared"); + + // Remove all extra feat entries + // Currently bonus objectives are not available, only goals + var bonusTaskCount = 0; while (((IList)selectedMenu.Entries).Count > bonusTaskCount + 1) ((IList)selectedMenu.Entries).RemoveAt(bonusTaskCount + 1); - } } -} +} \ No newline at end of file diff --git a/TsRandomizer/Screens/GameplayScreen.cs b/TsRandomizer/Screens/GameplayScreen.cs index 4229474b..b8a6f53c 100644 --- a/TsRandomizer/Screens/GameplayScreen.cs +++ b/TsRandomizer/Screens/GameplayScreen.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Reflection; using Archipelago.MultiClient.Net.Enums; using Microsoft.Xna.Framework; @@ -22,8 +23,8 @@ namespace TsRandomizer.Screens { - [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.GameplayScreen")] - // ReSharper disable once UnusedMember.Global + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.GameplayScreen")] + // ReSharper disable once UnusedMember.Global class GameplayScreen : Screen { static readonly MethodInfo LoadingScreenLoadMethod = TimeSpinnerType @@ -104,6 +105,7 @@ public override void Initialize(ItemLocationMap _, GCM gameContentManager) ItemTrackerUplink.UpdateState(ItemTrackerState.FromItemLocationMap(ItemLocations)); ItemManipulator.Initialize(ItemLocations); + ItemManipulator.ApplyStackCap(Level.GameSave); if (settings.DamageRando.Value != "Off") OrbDamageManager.PopulateOrbLookups(Level.GameSave, settings.DamageRando.Value, settings.DamageRandoOverrides.Value); @@ -113,8 +115,8 @@ public override void Initialize(ItemLocationMap _, GCM gameContentManager) BestiaryManager.RefreshBossSaveFlags(Level); if (Seed.Options.Archipelago) - HandleArchipelago(settings); - + HandleArchipelago(settings); + #if DEBUG saveFile.DataKeyBools["TS_INSTAGIB"] = true; @@ -187,12 +189,52 @@ public override void Update(GameTime gameTime, InputState input) UpdateGenericScripts(Level); + if (QoLSettings.Current.AutoSkipCutscenes) + TrySkipCutscene(); + + if (QoLSettings.Current.AutoSkipDialogue) + TrySkipDialogue(); + if (QoLSettings.Current.StackCap != 9) + ItemManipulator.ApplyStackCap(Level.GameSave); + #if DEBUG TimespinnerAfterDark(input); #endif HandleCurrentGifts(); } + void TrySkipCutscene() + { + var level = Level; + if (level == null) return; + + var levelDynamic = LevelReflected; + if (!(bool)levelDynamic.IsPlayerInputBlocked) return; + if ((bool)levelDynamic.IsActiveScriptUnskippable) return; + if ((bool)levelDynamic.IsDoingPlayerDeathCutscene) return; + + var activeScripts = (IList)levelDynamic._activeScripts; + foreach (var script in activeScripts) + { + var s = script.AsDynamic(); + if (s.ScriptType.ToString() == "CutsceneStart" && (float)s.Arguments.X >= 1f) + { + levelDynamic.SkipCutscene(); + Dynamic._isShowingSkipCutscenePrompt = false; + return; + } + } + } + + void TrySkipDialogue() + { + var dialogueBox = (object)Dynamic._currentDialogue; + if (dialogueBox == null) return; + var d = dialogueBox.AsDynamic(); + if ((bool)d.IsFinished) return; + d.FinishDialogue(); + } + void UpdateGenericScripts(Level level) { if (hpCap <= level.MainHero.MaxHP) @@ -271,7 +313,7 @@ void DrawReceivedGifts(SpriteBatch spriteBatch, SpriteFont menuFont) } void DrawRoomId(SpriteBatch spriteBatch, SpriteFont menuFont) - { + { #if DEBUG if (ItemLocations == null) return; @@ -287,7 +329,6 @@ void DrawRoomId(SpriteBatch spriteBatch, SpriteFont menuFont) #endif } - public void HideItemPickupBar() => ((object)Dynamic._itemGetBanner).AsDynamic()._displayTimer = 3f; public void ChangeItemPickupBar(string name) => ((object)Dynamic._itemGetBanner).AsDynamic()._itemName = name; @@ -301,8 +342,8 @@ public void ShowItemPickupBar(string name) itemBanner.ItemCategory = EInventoryCategoryType.UseItem; itemBanner._itemName = name; itemBanner._displayTimer = 0f; - } - + } + #if DEBUG void TimespinnerAfterDark(InputState input) { @@ -317,4 +358,4 @@ void TimespinnerAfterDark(InputState input) } #endif } -} +} \ No newline at end of file diff --git a/TsRandomizer/Screens/Menu/QoLSettingsMenu.cs b/TsRandomizer/Screens/Menu/QoLSettingsMenu.cs new file mode 100644 index 00000000..9f7b2bfd --- /dev/null +++ b/TsRandomizer/Screens/Menu/QoLSettingsMenu.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using Microsoft.Xna.Framework; +using Timespinner.GameAbstractions; +using Timespinner.GameAbstractions.Saving; +using TsRandomizer.Extensions; +using TsRandomizer.Screens.Menu; +using TsRandomizer.IntermediateObjects; +using ScreenManager = Timespinner.GameStateManagement.ScreenManager.ScreenManager; +using GameScreen = Timespinner.GameStateManagement.ScreenManager.GameScreen; + +namespace TsRandomizer.Screens +{ + public static class QoLSettingsMenu + { + static readonly Type JournalMenuType = + TimeSpinnerType.Get("Timespinner.GameStateManagement.Screens.PauseMenu.JournalMenuScreen"); + static readonly Type MenuEntryType = + TimeSpinnerType.Get("Timespinner.GameStateManagement.MenuEntry"); + + public static bool IsQoLMenuPending { get; set; } + + internal static GameScreen Create(ScreenManager screenManager) + { + GCM gcm = screenManager.AsDynamic().GCM; + gcm.LoadAllResources(screenManager.AsDynamic().GeneralContentManager, screenManager.GraphicsDevice); + + void OnExit() { } + + return (GameScreen)Activator.CreateInstance(JournalMenuType, GameSave.DemoSave, gcm, (Action)OnExit); + } + + public static void BuildMenu(GameScreen gameScreen, int selectedIndex) + { + var d = gameScreen.AsDynamic(); + var emptyList = new object[0].ToList(MenuEntryType); + + d._menuTitle = "QoL Menu"; + + ((object)d._primaryMenuCollection).AsDynamic()._entries = emptyList; + ((object)d._memoriesInventoryCollection).AsDynamic()._entries = emptyList; + ((object)d._lettersInventoryCollection).AsDynamic()._entries = emptyList; + ((object)d._filesInventoryCollection).AsDynamic()._entries = emptyList; + ((object)d._questInventory).AsDynamic()._entries = emptyList; + ((object)d._bestiaryInventory).AsDynamic()._entries = emptyList; + ((object)d._featsInventory).AsDynamic()._entries = emptyList; + + var menuList = new object[0].ToList(MenuEntryType); + + void AddEntry(string label, Action onSelect, string description) + { + int idx = ((IList)menuList).Count; + var entry = MenuEntry.Create(label, _ => { onSelect(); BuildMenu(gameScreen, idx); }); + entry.IsCenterAligned = false; + entry.DoesDrawLargeShadow = false; + entry.Description = description; + ((IList)menuList).Add(entry.AsTimeSpinnerMenuEntry()); + } + + AddEntry(GetCutsceneLabel(), () => { QoLSettings.Current.AutoSkipCutscenes = !QoLSettings.Current.AutoSkipCutscenes; QoLSettings.Save(); }, "Automatically skips cutscenes without any button press."); + AddEntry(GetDialogueLabel(), () => { QoLSettings.Current.AutoSkipDialogue = !QoLSettings.Current.AutoSkipDialogue; QoLSettings.Save(); }, "Dialogue boxes advance instantly without input."); + AddEntry(GetStackCapLabel(), () => { var steps = new[] { 9, 25, 50, 99 }; var idx2 = Array.IndexOf(steps, QoLSettings.Current.StackCap); QoLSettings.Current.StackCap = steps[(idx2 + 1) % steps.Length]; QoLSettings.Save(); }, "Maximum stack size for consumable items. Click to cycle: 9, 25, 50, 99. Shop cap mirrors this value (restart required for shop to apply)."); + AddEntry(GetFastToastLabel(), () => { QoLSettings.Current.FastToastPopups = !QoLSettings.Current.FastToastPopups; QoLSettings.Save(); }, "Fast: toasts appear and disappear instantly. Vanilla: original timing."); + AddEntry(GetToastBlockLabel(), () => { QoLSettings.Current.ToastsBlockMovement = !QoLSettings.Current.ToastsBlockMovement; QoLSettings.Save(); }, "On: toasts freeze movement (vanilla). Off: toasts never block movement."); + + var questCol = ((object)d._questInventory); + questCol.AsDynamic()._entries = menuList; + d.ChangeMenuCollection(questCol, false); + ((object)d._selectedMenuCollection).AsDynamic().SetSelectedIndex(selectedIndex); + } + + static string GetCutsceneLabel() => + $"Auto Skip Cutscenes: {(QoLSettings.Current.AutoSkipCutscenes ? "On" : "Off")}"; + + static string GetDialogueLabel() => + $"Auto Skip Dialogue: {(QoLSettings.Current.AutoSkipDialogue ? "On" : "Off")}"; + + static string GetStackCapLabel() => + $"Stack Cap: {QoLSettings.Current.StackCap}"; + + static string GetFastToastLabel() => + $"Toast Popup Speed: {(QoLSettings.Current.FastToastPopups ? "Fast" : "Vanilla")}"; + + static string GetToastBlockLabel() => + $"Toasts Block Movement: {(QoLSettings.Current.ToastsBlockMovement ? "On" : "Off")}"; + } +} \ No newline at end of file diff --git a/TsRandomizer/Screens/OptionsMenuScreen.cs b/TsRandomizer/Screens/OptionsMenuScreen.cs index 76d9c0a4..caff442a 100644 --- a/TsRandomizer/Screens/OptionsMenuScreen.cs +++ b/TsRandomizer/Screens/OptionsMenuScreen.cs @@ -10,8 +10,8 @@ namespace TsRandomizer.Screens { - [TimeSpinnerType("Timespinner.GameStateManagement.Screens.PauseMenu.OptionsMenuScreen")] - // ReSharper disable once UnusedMember.Global + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.PauseMenu.OptionsMenuScreen")] + // ReSharper disable once UnusedMember.Global class OptionsMenuScreen : Screen { static readonly Type MainMenuEntryType = TimeSpinnerType.Get("Timespinner.GameStateManagement.MenuEntry"); @@ -23,6 +23,7 @@ public OptionsMenuScreen(ScreenManager screenManager, GameScreen gameScreen) : b public override void Initialize(ItemLocationMap itemLocationMap, GCM gameContentManager) { AddSettingButton(MenuEntry.Create("Randomizer Settings", OpenSettingsMenu)); + AddSettingButton(MenuEntry.Create("Randomizer QoL Settings", OpenQoLSettingsMenu)); } void AddSettingButton(MenuEntry settingButton) @@ -42,5 +43,12 @@ void OpenSettingsMenu(PlayerIndex playerIndex) ScreenManager.AddScreen(gameSettingsMenu, playerIndex); } + + void OpenQoLSettingsMenu(PlayerIndex playerIndex) + { + QoLSettingsMenu.IsQoLMenuPending = true; + var qolMenu = QoLSettingsMenu.Create(ScreenManager); + ScreenManager.AddScreen(qolMenu, playerIndex); + } } -} +} \ No newline at end of file diff --git a/TsRandomizer/Screens/Screen.cs b/TsRandomizer/Screens/Screen.cs index 12e3d109..db2d8ad2 100644 --- a/TsRandomizer/Screens/Screen.cs +++ b/TsRandomizer/Screens/Screen.cs @@ -75,6 +75,8 @@ public virtual void Update(GameTime gameTime, InputState input) { } + + public virtual void HandleInput(InputState input) { } public virtual void Draw(SpriteBatch spriteBatch, SpriteFont menuFont) { } diff --git a/TsRandomizer/Screens/ScreenManager.cs b/TsRandomizer/Screens/ScreenManager.cs index dffc950f..643ec5b5 100644 --- a/TsRandomizer/Screens/ScreenManager.cs +++ b/TsRandomizer/Screens/ScreenManager.cs @@ -19,20 +19,57 @@ class ScreenManager : Timespinner.GameStateManagement.ScreenManager.ScreenManage { static readonly Type GamePlayScreenType = TimeSpinnerType.Get("Timespinner.GameStateManagement.Screens.InGame.GameplayScreen"); + static readonly Type BaseToastPopupType = + TimeSpinnerType.Get("Timespinner.GameStateManagement.Screens.InGame.BaseToastPopup"); + + static readonly System.Reflection.FieldInfo FreezeField = + BaseToastPopupType.GetField("_doesFreezeGameplay", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.FieldInfo TimeToWaitField = + BaseToastPopupType.GetField("_timeToWait", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.FieldInfo TotalDisplayField = + BaseToastPopupType.GetField("_totalDisplayTime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.FieldInfo TimeBeforeFlashingField = + BaseToastPopupType.GetField("_timeBeforeFlashing", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.FieldInfo TimeToFlashField = + BaseToastPopupType.GetField("_timeToFlash", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.FieldInfo TimeToFadeField = + BaseToastPopupType.GetField("_timeToFade", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.PropertyInfo IsOverlayProp = + BaseToastPopupType.BaseType?.GetProperty("IsOverlayScreen", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.PropertyInfo WaitForInputProp = + BaseToastPopupType.GetProperty("DoesWaitForInputToFinish", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + static readonly System.Reflection.PropertyInfo HasReceivedInputProp = + BaseToastPopupType.GetProperty("HasReceivedInputToClose", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + static readonly string LevelUpToastTypeName = + "Timespinner.GameStateManagement.Screens.InGame.Toasts.CharacterLevelUpToast"; + static readonly System.Reflection.FieldInfo ControlTimerField = + TimeSpinnerType.Get(LevelUpToastTypeName) + .GetField("_controlTimer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + // Only patch toasts that relate to item/level pickups. + // AreaTitleToast (room name on screen transition) must be left alone. + static readonly HashSet PatchableToastTypeNames = new HashSet + { + "Timespinner.GameStateManagement.Screens.InGame.Toasts.RelicOrbGetToast", + "Timespinner.GameStateManagement.Screens.InGame.Toasts.CharacterLevelUpToast", + "Timespinner.GameStateManagement.Screens.InGame.Toasts.OrbLevelUpToast", + "Timespinner.GameStateManagement.Screens.InGame.Toasts.StatMaxUpToast", + "Timespinner.GameStateManagement.Screens.InGame.Toasts.QuestCompleteToast", + }; readonly LookupDictionary hookedScreens = new LookupDictionary(s => s.GameScreen); readonly List foundScreens = new List(20); + readonly HashSet toastScreensPatched = new HashSet(); ItemLocationMap itemLocationMap; public readonly dynamic Dynamic; - public GCM GameContentManager => Dynamic.GCM; - + public static Log Log; public static GameConsole Console; - public static bool IsConsoleOpen; public ScreenManager(TimespinnerGame game, PlatformHelper platformHelper) : base(game, platformHelper) @@ -43,34 +80,29 @@ public ScreenManager(TimespinnerGame game, PlatformHelper platformHelper) : base protected override void LoadContent() { base.LoadContent(); - GameContentManager.LatinFont.DefaultCharacter = '?'; - Log = new Log(); Console = new GameConsole(this, GameContentManager); - Console.AddCommand(new ConnectCommand(this)); } public override void Update(GameTime gameTime) { var input = (InputState)Dynamic._input; - DetectNewScreens(); UpdateScreens(gameTime, input); - Overlay.UpdateAll(gameTime, input, Jukebox); - if (input.IsNewKeyPress(Keys.OemTilde)) ToggleConsole(); - - base.Update(gameTime); + base.Update(gameTime); + // Apply AFTER base.Update so we overwrite whatever the toast's own + // Update just set — otherwise we lose the race every frame. + ForceToastSettings(); } public void ToggleConsole() { IsConsoleOpen = !IsConsoleOpen; - if (IsConsoleOpen) AddScreen(Console, null); else @@ -80,45 +112,70 @@ public void ToggleConsole() public override void Draw(GameTime gameTime) { base.Draw(gameTime); - DrawGameplayScreens(); - Overlay.DrawAll(SpriteBatch, new Rectangle(0, 0, ScreenSize.X, ScreenSize.Y), GameContentManager); } void DetectNewScreens() { foundScreens.Clear(); - foreach (var screen in GetScreens()) { if (hookedScreens.Contains(screen)) { foundScreens.Add(screen); - if (screen.GetType() == GamePlayScreenType) itemLocationMap = ((GameplayScreen)hookedScreens[screen]).ItemLocations; - continue; } - - if(!Screen.RegisteredTypes.TryGetValue(screen.GetType(), out var handlerType)) + if (!Screen.RegisteredTypes.TryGetValue(screen.GetType(), out var handlerType)) continue; - var screenHandler = (Screen)Activator.CreateInstance(handlerType, this, screen); hookedScreens.Add(screenHandler); foundScreens.Add(screen); - screenHandler.Initialize(itemLocationMap, GameContentManager); } - if (foundScreens.Count != hookedScreens.Count) hookedScreens.Filter(foundScreens, s => s.Unload()); } - void UpdateScreens(GameTime gameTime, InputState input) + void ForceToastSettings() { - foreach (var screen in hookedScreens) + foreach (var screen in GetScreens()) + if (BaseToastPopupType.IsInstanceOfType(screen) + && PatchableToastTypeNames.Contains(screen.GetType().FullName)) + ApplyToastSettings(screen); + } + + static void ApplyToastSettings(GameScreen screen) + { + if (QoLSettings.Current.FastToastPopups) + { + TimeToWaitField.SetValue(screen, 0f); + WaitForInputProp?.SetValue(screen, false); + HasReceivedInputProp?.SetValue(screen, true); + + float before = (float)TimeBeforeFlashingField.GetValue(screen); + float flash = (float)TimeToFlashField.GetValue(screen); + float fade = (float)TimeToFadeField.GetValue(screen); + TotalDisplayField.SetValue(screen, before + flash + fade); + } + + if (!QoLSettings.Current.ToastsBlockMovement) + { + FreezeField.SetValue(screen, false); + IsOverlayProp?.SetValue(screen, true); + + if (screen.GetType().FullName == LevelUpToastTypeName) + ControlTimerField?.SetValue(screen, 0.75f); + } + } + + void UpdateScreens(GameTime gameTime, InputState input) + { + foreach (var screen in hookedScreens) + screen.HandleInput(input); + foreach (var screen in hookedScreens) screen.Update(gameTime, input); } @@ -135,7 +192,6 @@ public void CopyScreensFrom(Timespinner.GameStateManagement.ScreenManager.Screen } public T FirstOrDefault() where T : Screen => (T)hookedScreens.FirstOrDefault(s => s.GetType() == typeof(T)); - public GameScreen FirstOrDefaultTimespinnerOfType(Type type) => ((List)Dynamic._screens).FirstOrDefault(s => s.GetType() == type); } -} +} \ No newline at end of file diff --git a/TsRandomizer/Screens/ShopMenuScreen.cs b/TsRandomizer/Screens/ShopMenuScreen.cs index b81890b7..ccdf6073 100644 --- a/TsRandomizer/Screens/ShopMenuScreen.cs +++ b/TsRandomizer/Screens/ShopMenuScreen.cs @@ -1,48 +1,240 @@ -using System.Collections; -using System.Linq; -using Timespinner.GameAbstractions.Inventory; -using Timespinner.GameAbstractions.Saving; -using Timespinner.GameStateManagement.ScreenManager; -using TsRandomizer.Extensions; -using TsRandomizer.IntermediateObjects; - -namespace TsRandomizer.Screens -{ - [TimeSpinnerType("Timespinner.GameStateManagement.Screens.Shop.ShopMenuScreen")] - // ReSharper disable once UnusedMember.Global - class ShopMenuScreen : Screen - { - public ShopMenuScreen(ScreenManager screenManager, GameScreen screen) : base(screenManager, screen) - { - var gameSettings = ((GameSave)Dynamic._saveFile).GetSettings(); - - // Menu count varies on relics/items/equipment etc. being in inventory - // Last menu is always helper functions that don't have an _items - // but aren't otherwise distinguishable - foreach (var i in Enumerable.Range(0, ((IList)Dynamic._subMenuCollections).Count - 1)) - { - var shopMenu = ((IList)Dynamic._subMenuCollections)[i].AsDynamic(); - foreach (var shopMenuEntry in shopMenu._items) - { - var dynamicShopMenuEntry = ((object)shopMenuEntry).AsDynamic(); - - var item = (InventoryItem)dynamicShopMenuEntry.Item; - if (item.NameKey == "inv_use_MagicMarbles") - { - item.IsSellable = false; - dynamicShopMenuEntry.ShopPrice = -1; - } - - int currentPrice = dynamicShopMenuEntry.ShopPrice; - if (currentPrice == 0) - { - // Set a price for "priceless" items - dynamicShopMenuEntry.ShopPrice = 2000; - currentPrice = dynamicShopMenuEntry.ShopPrice; - } - dynamicShopMenuEntry.ShopPrice = (int)(currentPrice * gameSettings.ShopMultiplier.Value); - } - } - } - } -} +using System.Collections; +using System.Linq; +using System.Reflection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using Timespinner.GameAbstractions.Inventory; +using Timespinner.GameAbstractions.Saving; +using Timespinner.GameStateManagement.ScreenManager; +using TsRandomizer.Extensions; +using TsRandomizer.IntermediateObjects; +using System; + +namespace TsRandomizer.Screens +{ + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.Shop.ShopMenuScreen")] + // ReSharper disable once UnusedMember.Global + class ShopMenuScreen : Screen + { + static readonly MethodInfo GetSelectedCategoryMethod = TimeSpinnerType + .Get("Timespinner.GameStateManagement.Screens.Shop.ShopMenuScreen") + .GetMethod("GetSelectedCategory", BindingFlags.NonPublic | BindingFlags.Instance); + + static readonly MethodInfo GetSelectedShopEntryMethod = TimeSpinnerType + .Get("Timespinner.GameStateManagement.Screens.Shop.ShopMenuEntryCollection") + .GetMethod("GetSelectedShopEntry", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + static readonly Type MenuDescriptionType = TimeSpinnerType + .Get("Timespinner.GameStateManagement.Screens.BaseClasses.Menu.MenuDescription"); + + static readonly Type EInventoryItemIconType = TimeSpinnerType + .Get("Timespinner.GameAbstractions.Inventory.EInventoryItemIcon"); + + // Timers for held-button repeat on quantity adjustment + float _rightHeldTime; + float _leftHeldTime; + const float HoldDelay = 0.4f; + const float HoldRepeat = 0.1f; + float _rightLastRepeat; + float _leftLastRepeat; + + public ShopMenuScreen(ScreenManager screenManager, GameScreen screen) : base(screenManager, screen) + { + var gameSettings = ((GameSave)Dynamic._saveFile).GetSettings(); + + // Menu count varies on relics/items/equipment etc. being in inventory + // Last menu is always helper functions that don't have an _items + // but aren't otherwise distinguishable + foreach (var i in Enumerable.Range(0, ((IList)Dynamic._subMenuCollections).Count - 1)) + { + var shopMenu = ((IList)Dynamic._subMenuCollections)[i].AsDynamic(); + foreach (var shopMenuEntry in shopMenu._items) + { + var dynamicShopMenuEntry = ((object)shopMenuEntry).AsDynamic(); + var item = (InventoryItem)dynamicShopMenuEntry.Item; + if (item.NameKey == "inv_use_MagicMarbles") + { + item.IsSellable = false; + dynamicShopMenuEntry.ShopPrice = -1; + } + int currentPrice = dynamicShopMenuEntry.ShopPrice; + if (currentPrice == 0) + { + // Set a price for "priceless" items + dynamicShopMenuEntry.ShopPrice = 2000; + currentPrice = dynamicShopMenuEntry.ShopPrice; + } + dynamicShopMenuEntry.ShopPrice = (int)(currentPrice * gameSettings.ShopMultiplier.Value); + + if (item is InventoryUseItem useItem) + { + var type = useItem.UseItemType; + if (type != EInventoryUseItemType.MagicMarbles + && type != EInventoryUseItemType.EssenceCrystal + && type != EInventoryUseItemType.GoldRing + && type != EInventoryUseItemType.GoldNecklace) + useItem.StackCap = QoLSettings.Current.StackCap; + } + } + } + } + + public override void Update(GameTime gameTime, InputState input) + { + int cap = QoLSettings.Current.StackCap; + if (cap <= 9) return; + + bool isBuying = (bool)Dynamic._isBuying; + if (!isBuying) return; + + float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; + + // Detect raw held state for repeat logic + bool rightHeld = false; + bool leftHeld = false; + for (int i = 0; i < input.CurrentGamePadStates.Length; i++) + { + rightHeld |= input.CurrentGamePadStates[i].IsButtonDown(Buttons.DPadRight) + || input.CurrentGamePadStates[i].IsButtonDown(Buttons.LeftThumbstickRight) + || input.CurrentGamePadStates[i].IsButtonDown(Buttons.RightThumbstickRight); + leftHeld |= input.CurrentGamePadStates[i].IsButtonDown(Buttons.DPadLeft) + || input.CurrentGamePadStates[i].IsButtonDown(Buttons.LeftThumbstickLeft) + || input.CurrentGamePadStates[i].IsButtonDown(Buttons.RightThumbstickLeft); + } + for (int i = 0; i < input.CurrentKeyboardStates.Length; i++) + { + rightHeld |= input.CurrentKeyboardStates[i].IsKeyDown(Keys.Right); + leftHeld |= input.CurrentKeyboardStates[i].IsKeyDown(Keys.Left); + } + + bool rightNew = input.IsNewButtonPress(Buttons.DPadRight) + || input.IsNewButtonPress(Buttons.LeftThumbstickRight) + || input.IsNewButtonPress(Buttons.RightThumbstickRight) + || input.IsNewKeyPress(Keys.Right); + + bool leftNew = input.IsNewButtonPress(Buttons.DPadLeft) + || input.IsNewButtonPress(Buttons.LeftThumbstickLeft) + || input.IsNewButtonPress(Buttons.RightThumbstickLeft) + || input.IsNewKeyPress(Keys.Left); + + // Update hold timers + if (rightHeld) + { + _rightHeldTime += dt; + if (rightNew) _rightLastRepeat = 0f; + } + else + { + _rightHeldTime = 0f; + _rightLastRepeat = 0f; + } + + if (leftHeld) + { + _leftHeldTime += dt; + if (leftNew) _leftLastRepeat = 0f; + } + else + { + _leftHeldTime = 0f; + _leftLastRepeat = 0f; + } + + // Fire on initial press or after hold delay with repeat interval + bool rightPressed = rightNew + || (rightHeld && _rightHeldTime >= HoldDelay && _rightHeldTime - _rightLastRepeat >= HoldRepeat); + bool leftPressed = leftNew + || (leftHeld && _leftHeldTime >= HoldDelay && _leftHeldTime - _leftLastRepeat >= HoldRepeat); + + if (rightPressed && _rightHeldTime >= HoldDelay) _rightLastRepeat = _rightHeldTime; + if (leftPressed && _leftHeldTime >= HoldDelay) _leftLastRepeat = _leftHeldTime; + + if (!rightPressed && !leftPressed) return; + + var selectedCategory = GetSelectedCategoryMethod?.Invoke(GameScreen, null); + if (selectedCategory == null) return; + + var selectedEntry = GetSelectedShopEntryMethod?.Invoke(selectedCategory, null); + if (selectedEntry == null) return; + + var entryDynamic = ((object)selectedEntry).AsDynamic(); + var item = (InventoryItem)entryDynamic.Item; + if (!(item is InventoryUseItem useItem2)) return; + + var type2 = useItem2.UseItemType; + if (type2 == EInventoryUseItemType.MagicMarbles + || type2 == EInventoryUseItemType.EssenceCrystal + || type2 == EInventoryUseItemType.GoldRing + || type2 == EInventoryUseItemType.GoldNecklace) + return; + + var save = (GameSave)Dynamic._saveFile; + int currentCount = save.Inventory.UseItemInventory.Inventory.ContainsKey(item.Key) + ? save.Inventory.UseItemInventory.Inventory[item.Key].Count + : 0; + + int maxCanBuy = cap - currentCount; + if (maxCanBuy <= 0) return; + + int currentQty = (int)entryDynamic.QuanityToBuy; + // Vanilla cap is 9 total, so vanilla allows buying up to (9 - currentCount) + int vanillaCap = 9 - currentCount; + + // Only act when we are already at or above the vanilla cap, + // so below that the vanilla input handling works as normal. + if (rightPressed && currentQty >= vanillaCap && currentQty < maxCanBuy) + entryDynamic.QuanityToBuy = currentQty + 1; + + if (leftPressed && currentQty > vanillaCap && currentQty != 1) + entryDynamic.QuanityToBuy = currentQty - 1; + } + + public override void HandleInput(InputState input) + { + int cap = QoLSettings.Current.StackCap; + if (cap <= 9) return; + + bool isBuying = (bool)Dynamic._isBuying; + if (!isBuying) return; + + if (!input.IsNewPressConfirm(null)) return; + + var selectedCategory = GetSelectedCategoryMethod?.Invoke(GameScreen, null); + if (selectedCategory == null) return; + + var selectedEntry = GetSelectedShopEntryMethod?.Invoke(selectedCategory, null); + if (selectedEntry == null) return; + + var entryDynamic = ((object)selectedEntry).AsDynamic(); + var item = (InventoryItem)entryDynamic.Item; + if (!(item is InventoryUseItem)) return; + + var save = (GameSave)Dynamic._saveFile; + int currentCount = save.Inventory.UseItemInventory.Inventory.ContainsKey(item.Key) + ? save.Inventory.UseItemInventory.Inventory[item.Key].Count + : 0; + + if (currentCount < 9) return; + + // Player holds 9+ and tries to confirm - show message immediately in HandleInput phase + Dynamic.PlayErrorSound(); + var noneIcon = Enum.ToObject(EInventoryItemIconType, 0); + var ctor = MenuDescriptionType.GetConstructors( + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (ctor.Length > 0) + { + var menuDesc = ctor[0].Invoke(new object[] + { + "Vanilla cap reached - hold fewer than 9 to buy more. (We cannot fix this, sorry!)", + (object)Dynamic.DescriptionFont, + noneIcon, + (object)Dynamic.GCM.SpMenuIcons, + (object)Dynamic.GCM.SpUIButtons, + (bool)Dynamic.IsDescriptionCentered, + (object)Dynamic.DescriptionControllerMapping + }); + Dynamic.CurrentDescription = menuDesc; + } + } + } +} \ No newline at end of file diff --git a/TsRandomizer/Screens/ToastPopupScreen.cs b/TsRandomizer/Screens/ToastPopupScreen.cs new file mode 100644 index 00000000..2aa1065d --- /dev/null +++ b/TsRandomizer/Screens/ToastPopupScreen.cs @@ -0,0 +1,102 @@ +using Microsoft.Xna.Framework; +using Timespinner.GameAbstractions; +using Timespinner.GameStateManagement.ScreenManager; +using TsRandomizer.IntermediateObjects; +using TsRandomizer.Randomisation; + +namespace TsRandomizer.Screens +{ + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.BaseToastPopup")] + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.Toasts.RelicOrbGetToast")] + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.Toasts.CharacterLevelUpToast")] + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.Toasts.OrbLevelUpToast")] + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.Toasts.StatMaxUpToast")] + [TimeSpinnerType("Timespinner.GameStateManagement.Screens.InGame.Toasts.QuestCompleteToast")] + // ReSharper disable once UnusedMember.Global + class ToastPopupScreen : Screen + { + static readonly string BaseToastTypeName = + "Timespinner.GameStateManagement.Screens.InGame.BaseToastPopup"; + static readonly string LevelUpToastTypeName = + "Timespinner.GameStateManagement.Screens.InGame.Toasts.CharacterLevelUpToast"; + + // Cached via reflection once, reused across all toast instances + static System.Reflection.FieldInfo _freezeField; + static System.Reflection.FieldInfo _timeToWaitField; + static System.Reflection.FieldInfo _totalDisplayField; + static System.Reflection.FieldInfo _timeBeforeFlashingField; + static System.Reflection.FieldInfo _timeToFlashField; + static System.Reflection.FieldInfo _timeToFadeField; + static System.Reflection.PropertyInfo _isOverlayProp; + static System.Reflection.PropertyInfo _waitForInputProp; + static System.Reflection.PropertyInfo _hasReceivedInputProp; + static System.Reflection.FieldInfo _controlTimerField; + + bool _isLevelUpToast; + + public ToastPopupScreen(ScreenManager screenManager, GameScreen gameScreen) + : base(screenManager, gameScreen) + { + } + + public override void Initialize(ItemLocationMap itemLocationMap, GCM gameContentManager) + { + _isLevelUpToast = GameScreen.GetType().FullName == LevelUpToastTypeName; + + var flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance; + var publicFlags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance; + + if (_freezeField == null) + { + var baseType = TimeSpinnerType.Get(BaseToastTypeName); + var levelUpType = TimeSpinnerType.Get(LevelUpToastTypeName); + + _freezeField = baseType.GetField("_doesFreezeGameplay", flags); + _timeToWaitField = baseType.GetField("_timeToWait", flags); + _totalDisplayField = baseType.GetField("_totalDisplayTime", flags); + _timeBeforeFlashingField = baseType.GetField("_timeBeforeFlashing", flags); + _timeToFlashField = baseType.GetField("_timeToFlash", flags); + _timeToFadeField = baseType.GetField("_timeToFade", flags); + _isOverlayProp = GameScreen.GetType().BaseType?.GetProperty("IsOverlayScreen", publicFlags); + _waitForInputProp = baseType.GetProperty("DoesWaitForInputToFinish", publicFlags); + _hasReceivedInputProp = baseType.GetProperty("HasReceivedInputToClose", publicFlags); + _controlTimerField = levelUpType.GetField("_controlTimer", flags); + } + + // FastToastPopups: collapse the wait phase to zero so the toast flies through + if (QoLSettings.Current.FastToastPopups) + { + _timeToWaitField.SetValue(GameScreen, 0f); + + float before = (float)_timeBeforeFlashingField.GetValue(GameScreen); + float flash = (float)_timeToFlashField.GetValue(GameScreen); + float fade = (float)_timeToFadeField.GetValue(GameScreen); + _totalDisplayField.SetValue(GameScreen, before + flash + fade); + } + + // ToastsBlockMovement: control whether the toast freezes gameplay + if (!QoLSettings.Current.ToastsBlockMovement) + { + _freezeField.SetValue(GameScreen, false); + _isOverlayProp?.SetValue(GameScreen, true); + + // CharacterLevelUpToast has its own _controlTimer that stalls input + if (_isLevelUpToast) + _controlTimerField?.SetValue(GameScreen, 0.75f); + } + } + + public override void Update(GameTime gameTime, InputState input) + { + // FastToastPopups suppresses the button-press requirement. + // The original Harmony patch blocked set_DoesWaitForInputToFinish globally + // on all toasts. We replicate that by overriding every frame, since + // RelicOrbGetToast sets it back to true during its own update/script handling. + if (QoLSettings.Current.FastToastPopups) + { + _waitForInputProp?.SetValue(GameScreen, false); + _hasReceivedInputProp?.SetValue(GameScreen, true); + } + } + } +} \ No newline at end of file diff --git a/TsRandomizer/Settings/QoLSettings.cs b/TsRandomizer/Settings/QoLSettings.cs new file mode 100644 index 00000000..53be3d89 --- /dev/null +++ b/TsRandomizer/Settings/QoLSettings.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using Newtonsoft.Json; + +namespace TsRandomizer +{ + public class QoLSettingsData + { + public bool AutoSkipCutscenes { get; set; } = true; + public bool AutoSkipDialogue { get; set; } = true; + public int StackCap { get; set; } = 99; + public bool FastToastPopups { get; set; } = true; + public bool ToastsBlockMovement { get; set; } = false; + } + + public static class QoLSettings + { + static readonly string SettingsPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "qol-settings.json"); + + public static QoLSettingsData Current { get; private set; } = Load(); + + public static QoLSettingsData Load() + { + try + { + if (File.Exists(SettingsPath)) + { + var json = File.ReadAllText(SettingsPath); + Current = JsonConvert.DeserializeObject(json) ?? new QoLSettingsData(); + } + else + { + Current = new QoLSettingsData(); + Save(); + } + } + catch + { + Current = new QoLSettingsData(); + } + + return Current; + } + + public static void Save() + { + try + { + var json = JsonConvert.SerializeObject(Current, Formatting.Indented); + File.WriteAllText(SettingsPath, json); + } + catch + { + // ignored + } + } + } +} diff --git a/TsRandomizer/TsRandomizer.csproj b/TsRandomizer/TsRandomizer.csproj index c9aad11f..23f74900 100644 --- a/TsRandomizer/TsRandomizer.csproj +++ b/TsRandomizer/TsRandomizer.csproj @@ -1,421 +1,422 @@ - - - - - Debug - AnyCPU - {694E46C5-FD46-4CC8-8B71-D381E154CCF7} - WinExe - TsRandomizer - TsRandomizer - v4.8 - 512 - true - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - x86 - true - full - false - ..\..\TestVersions\SteamVerison\ - TRACE;DEBUG;NET40 - prompt - 4 - false - true - - - x86 - pdbonly - true - C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ - TRACE - prompt - 4 - false - true - - - - - - GunOrbLargeT.ico - - - true - ..\..\TestVersions\DRMFreeVersion\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - false - - - ..\..\TestVersions\DRMFreeVersion\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - false - - - true - ..\..\TestVersions\DRMFreeGoG\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - MinimumRecommendedRules.ruleset - - - ..\..\TestVersions\DRMFreeGoG\ - TRACE - true - true - pdbonly - x86 - 7.3 - prompt - - - - ..\packages\Archipelago.Gifting.Net.0.4.3\lib\net452\Archipelago.Gifting.Net.dll - - - ..\packages\Archipelago.MultiClient.Net.6.7.0\lib\net45\Archipelago.MultiClient.Net.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Timespinner\FNA.dll - ~/.local/share/Steam/steamapps/common/Timespinner/FNA.dll - ..\..\..\TestVersions\DRMFreeVersion\FNA.dll - False - - - - ..\packages\Archipelago.MultiClient.Net.6.7.0\lib\net45\Newtonsoft.Json.dll - - - - - False - - - ..\..\..\TestVersions\DRMFreeVersion\Timespinner.exe - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - + + + + + Debug + AnyCPU + {694E46C5-FD46-4CC8-8B71-D381E154CCF7} + WinExe + TsRandomizer + TsRandomizer + v4.8 + 512 + true + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + + x86 + true + full + false + ..\..\TestVersions\SteamVerison\ + TRACE;DEBUG;NET40 + prompt + 4 + false + true + + + x86 + pdbonly + true + C:\Program Files %28x86%29\Steam\steamapps\common\Timespinner\ + TRACE + prompt + 4 + false + true + + + + + + GunOrbLargeT.ico + + + true + ..\..\TestVersions\DRMFreeVersion\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + false + + + ..\..\TestVersions\DRMFreeVersion\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + false + + + true + ..\..\TestVersions\DebugDRMFreeGoG\ + TRACE + full + x86 + 7.3 + prompt + MinimumRecommendedRules.ruleset + + + ..\..\TestVersions\ReleaseDRMFreeGoG\ + TRACE + false + false + pdbonly + x86 + 7.3 + prompt + + + + ..\packages\Archipelago.Gifting.Net.0.4.4\lib\net452\Archipelago.Gifting.Net.dll + + + ..\packages\Archipelago.MultiClient.Net.6.7.0\lib\net45\Archipelago.MultiClient.Net.dll + + + False + ..\..\TestVersions\DebugDRMFreeGoG\FNA.dll + + + + ..\packages\Archipelago.MultiClient.Net.6.7.0\lib\net45\Newtonsoft.Json.dll + + + + + False + + + False + ..\..\TestVersions\DebugDRMFreeGoG\Timespinner.exe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + \ No newline at end of file diff --git a/TsRandomizer/TsRandomizer.csproj.user b/TsRandomizer/TsRandomizer.csproj.user new file mode 100644 index 00000000..ca342d1e --- /dev/null +++ b/TsRandomizer/TsRandomizer.csproj.user @@ -0,0 +1,6 @@ + + + + ShowAllFiles + + \ No newline at end of file diff --git a/TsRandomizer/packages.config b/TsRandomizer/packages.config index 6cc40bbd..f990d3b3 100644 --- a/TsRandomizer/packages.config +++ b/TsRandomizer/packages.config @@ -1,5 +1,6 @@ - - - - + + + + + \ No newline at end of file