diff --git a/Riders.Tweakbox/Controllers/CustomGearController/CustomGearCodePatcher.cs b/Riders.Tweakbox/Controllers/CustomGearController/CustomGearCodePatcher.cs index befac252..b1164e76 100644 --- a/Riders.Tweakbox/Controllers/CustomGearController/CustomGearCodePatcher.cs +++ b/Riders.Tweakbox/Controllers/CustomGearController/CustomGearCodePatcher.cs @@ -1,5 +1,5 @@ using Reloaded.Memory.Pointers; -using Sewer56.SonicRiders.Structures.Gameplay; +using ExtremeGear = Sewer56.SonicRiders.Structures.Gameplay.ExtremeGear; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -14,6 +14,10 @@ using Sewer56.SonicRiders; using Riders.Tweakbox.Interfaces.Structs.Gears; using CustomGearDataInternal = Riders.Tweakbox.Controllers.CustomGearController.Structs.Internal.CustomGearDataInternal; +using Riders.Tweakbox.Configs; +using Riders.Tweakbox.Misc; +using Sewer56.SonicRiders.API; +using Sewer56.SonicRiders.Structures.Enums; namespace Riders.Tweakbox.Controllers.CustomGearController; @@ -46,7 +50,7 @@ internal unsafe class CustomGearCodePatcher // Private members private Logger _log = new Logger(LogCategory.CustomGear); - + internal CustomGearCodePatcher() { MakeMaxGearAsmPatches(); @@ -69,6 +73,9 @@ internal CustomGearCodePatcher() UpdateGearCount(OriginalGearCount); PatchOpcodes(); PatchBoundsChecks(); + + // Respect save data + EventController.OnEnterCharacterSelect += LoadUnlockedGearsFromSave; } /// @@ -82,7 +89,7 @@ internal void AddGear(CustomGearDataInternal data) ref var gear = ref data.GearData; var gearType = gear.GearType; _newGears[GearCount] = gear; - _gearToModelMap[GearCount] = (byte)gear.GearModel; + _gearToModelMap[GearCount] = (byte)GetFreeGearModelIndex(); var gearIndex = GearCount; UpdateGearCount(GearCount + 1); @@ -90,6 +97,30 @@ internal void AddGear(CustomGearDataInternal data) PatchMaxGearId(gearIndex); } + /// + /// Gets the next unused index for an .

+ /// Intended for assigning arbitrary indices to Custom Extreme Gear for the , such that they can be unlocked independently of the Extreme Gear used for the physical model. + ///
+ /// + /// + internal int GetFreeGearModelIndex() + { + bool[] reservedGearModelIndices = GC.AllocateArray(MaxGearCount); + for (int x = 0; x <= GearCount; x++) + { + int modelIndex = _gearToModelMap[x]; + reservedGearModelIndices[modelIndex] = true; + } + + for (int x = 0; x < reservedGearModelIndices.Length; x++) + { + if (!reservedGearModelIndices[x]) + return x; + } + + throw new InvalidOperationException("Failed to identify a free gear model index. All possible indices are reserved."); + } + /// /// Resets all custom gear data. /// @@ -169,6 +200,37 @@ private void SetupNewPointers(string pointerName, ref T[] output, ref FixedAr } } + /// + /// Sets the "Unlocked" state of original game's gears to the currently loaded save file. + /// Any custom gears will be automatically unlocked. + /// + private void LoadUnlockedGearsFromSave() + { + // If "Boot to Menu" is enabled, then everything is unlocked anyway, so we don't need to check for saved data. + var tweakboxConf = IoC.GetSingleton(); + if (tweakboxConf.Data.BootToMenu) + return; + + // Reference to the vanilla location for storing gear unlock state + RefFixedArrayPtr vanillaUnlockedGearModels = new RefFixedArrayPtr((ulong)0x017BE4E8, (int)ExtremeGearModel.Cannonball + 1); + for (var x = 0; x < vanillaUnlockedGearModels.Count; x++) + { + bool isUnlocked = vanillaUnlockedGearModels[x]; + State.UnlockedGearModels[x] = isUnlocked; + } + + // Unlock any remaining custom gears + int firstFreeGearModelSlot = (int)ExtremeGearModel.Berserker + 1; + for (var x = firstFreeGearModelSlot; x < State.UnlockedGearModels.Count; x++) + { + // If it's defined in the Enum, it's a Vanilla gear, thus managed by the save. + if (Enum.IsDefined(typeof(ExtremeGearModel), (byte) x)) + continue; + + State.UnlockedGearModels[x] = true; + } + } + private void UpdateGearCount(int newCount) { GearCount = newCount;