Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve crash during gear selection when BootToMenu is disabled #31

Merged
merged 4 commits into from
Aug 15, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -46,7 +50,7 @@ internal unsafe class CustomGearCodePatcher

// Private members
private Logger _log = new Logger(LogCategory.CustomGear);

internal CustomGearCodePatcher()
{
MakeMaxGearAsmPatches();
Expand All @@ -69,6 +73,9 @@ internal CustomGearCodePatcher()
UpdateGearCount(OriginalGearCount);
PatchOpcodes();
PatchBoundsChecks();

// Respect save data
EventController.OnEnterCharacterSelect += LoadUnlockedGearsFromSave;
}

/// <summary>
Expand All @@ -82,14 +89,38 @@ 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);
data.SetGearIndex(gearIndex);
PatchMaxGearId(gearIndex);
}

/// <summary>
/// Gets the next unused index for an <see cref="ExtremeGearModel"/>.<br></br>
/// Intended for assigning arbitrary indices to Custom Extreme Gear for the <see cref="_gearToModelMap"/>, such that they can be unlocked independently of the Extreme Gear used for the physical model.
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
internal int GetFreeGearModelIndex()
{
bool[] reservedGearModelIndices = GC.AllocateArray<bool>(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.");
}

/// <summary>
/// Resets all custom gear data.
/// </summary>
Expand Down Expand Up @@ -169,6 +200,37 @@ private void SetupNewPointers<T>(string pointerName, ref T[] output, ref FixedAr
}
}

/// <summary>
/// Sets the "Unlocked" state of original game's gears to the currently loaded save file.
/// Any custom gears will be automatically unlocked.
/// </summary>
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<TweakboxConfig>();
if (tweakboxConf.Data.BootToMenu)
return;

// Reference to the vanilla location for storing gear unlock state
RefFixedArrayPtr<bool> vanillaUnlockedGearModels = new RefFixedArrayPtr<bool>((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;
Expand Down