Skip to content

Commit

Permalink
Fix compatability with CustomJSONData
Browse files Browse the repository at this point in the history
Infinite mode wasn't working when the CustomJSONData plugin was installed.

When starting infinite mode, Infinite Beat Saber would call `BeatmapData.InsertBeatmapEventDataInOrder` which would lead to the exception:

```
KeyNotFoundException: The given key 'CustomJSONData.CustomBeatmap.CustomBPMChangeBeatmapEventData' was not present in the dictionary.
```

The problem was that CustomJSONData converted all of the map's `BeatmapDataItems` to its own `BeatmapDataItem` subclasses (e.g. `CustomBPMChangeBeatmapEventData`). Part of Beat Saber's `InsertBeatmapEventDataInOrder` implementation blows up when given a `BeatmapDataItem` with which it isn't familiar.

`BeatmapRemixer.AddCustomJSONDataProcessors` fixes this problem. See that method's comment for details.

The solution required Infinite Beat Saber to depend on CustomJSONData and depending on CustomJSONData required updating Infinite Beat Saber from .NET Framework 4.7.2 to 4.8.
  • Loading branch information
rigdern committed Sep 25, 2023
1 parent cf8248d commit 81277f4
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 8 deletions.
8 changes: 7 additions & 1 deletion Eval/Eval.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Eval</RootNamespace>
<AssemblyName>Eval</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
Expand All @@ -20,6 +20,7 @@
<!--<PathMap>$(AppOutputBase)=X:\$(AssemblyName)\</PathMap>-->
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>false</Optimize>
Expand Down Expand Up @@ -57,6 +58,11 @@
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="CustomJSONData">
<HintPath>$(BeatSaberDir)\Plugins\CustomJSONData.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="InfiniteBeatSaber">
<HintPath>$(BeatSaberDir)\Plugins\InfiniteBeatSaber.dll</HintPath>
<Private>False</Private>
Expand Down
36 changes: 34 additions & 2 deletions InfiniteBeatSaber/BeatmapRemixer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using CustomJSONData.CustomBeatmap;
using InfiniteBeatSaber.Extensions;
using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -18,6 +21,7 @@ public BeatmapRemixer(IReadonlyBeatmapData beatmap, BeatmapData remixedBeatmap)
(_sortedBeatmapDataItems, _sortedObstacleDataItems) = FilterAndSortBeatmapDataItems(beatmap.allBeatmapDataItems);
_remixedBeatmap = remixedBeatmap;

AddCustomJSONDataProcessors(_remixedBeatmap);
AddBeatmapDataItemsInOrder(_remixedBeatmap, MapPrologue(_sortedBeatmapDataItems));
}

Expand All @@ -31,6 +35,35 @@ public void AddRemix(Remix remix)
}
}

// Adds data processors for the `BeatmapDataItems` provided by CustomJSONData (https://github.com/Aeroluna/CustomJSONData).
//
// Without these, methods like `BeatmapData.AddBeatmapObjectDataInOrder` throw an exception when you attempt to add one of
// CustomJSONData's `BeatmapDataItems` (e.g. `CustomBPMChangeBeatmapEventData`). This is because Beat Saber only installs
// data processors for the `BeatmapDataItems` that it provides.
//
// Because each CustomJSONData `BeatmapDataItem` is a subclass of one provided by Beat Saber that just extends it to implement
// `ICustomData`, it seems sensible for each CustomJSONData `BeatmapDataItem` to reuse the relevant Beat Saber data processor
// (e.g. `CustomBPMChangeBeatmapEventData` reuses the data processor of `BPMChangeBeatmapEventData`).
//
// For the list of data processors that Beat Saber installs, see the initialization of
// `BeatmapDataSortedListForTypeAndIds._sortedListsDataProcessors`.
private static void AddCustomJSONDataProcessors(BeatmapData beatmapData)
{
var beatmapDataItemsPerTypeAndId = beatmapData.GetField<BeatmapDataSortedListForTypeAndIds<BeatmapDataItem>, BeatmapData>("_beatmapDataItemsPerTypeAndId");
var dataProcessors = beatmapDataItemsPerTypeAndId.GetField<Dictionary<Type, ISortedListItemProcessor<BeatmapDataItem>>, BeatmapDataSortedListForTypeAndIds<BeatmapDataItem>>("_sortedListsDataProcessors");

dataProcessors.AddIfMissing(typeof(CustomBasicBeatmapEventData), dataProcessors[typeof(BasicBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomBPMChangeBeatmapEventData), dataProcessors[typeof(BPMChangeBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomColorBoostBeatmapEventData), dataProcessors[typeof(ColorBoostBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomLightColorBeatmapEventData), dataProcessors[typeof(LightColorBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomLightRotationBeatmapEventData), dataProcessors[typeof(LightRotationBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomNoteData), dataProcessors[typeof(NoteData)]);
dataProcessors.AddIfMissing(typeof(CustomObstacleData), dataProcessors[typeof(ObstacleData)]);
dataProcessors.AddIfMissing(typeof(CustomSliderData), dataProcessors[typeof(SliderData)]);
dataProcessors.AddIfMissing(typeof(CustomSpawnRotationBeatmapEventdata), dataProcessors[typeof(SpawnRotationBeatmapEventData)]);
dataProcessors.AddIfMissing(typeof(CustomWaypointData), dataProcessors[typeof(WaypointData)]);
}

private static (IEnumerable<BeatmapDataItem>, IEnumerable<ObstacleData>) FilterAndSortBeatmapDataItems(IEnumerable<BeatmapDataItem> beatmapDataItems)
{
var omittedItemTypes = new SortedSet<string>();
Expand Down Expand Up @@ -90,7 +123,6 @@ private static (IEnumerable<BeatmapDataItem>, IEnumerable<ObstacleData>) FilterA
return (keptBeatmapDataItems, obstacleDataItems);
}


private static void SetTime(BeatmapDataItem item, float time)
{
var fieldInfo = typeof(BeatmapDataItem).GetField("<time>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
Expand Down
15 changes: 15 additions & 0 deletions InfiniteBeatSaber/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace InfiniteBeatSaber.Extensions
{
internal static class DictionaryExtensions
{
public static void AddIfMissing<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
}
}
}
}
9 changes: 8 additions & 1 deletion InfiniteBeatSaber/InfiniteBeatSaber.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>InfiniteBeatSaber</RootNamespace>
<AssemblyName>InfiniteBeatSaber</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
Expand All @@ -20,6 +20,7 @@
<!--<PathMap>$(AppOutputBase)=X:\$(AssemblyName)\</PathMap>-->
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>false</Optimize>
Expand Down Expand Up @@ -50,6 +51,11 @@
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="CustomJSONData">
<HintPath>$(BeatSaberDir)\Plugins\CustomJSONData.dll</HintPath>
<Private>False</Private>
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<Private>False</Private>
<HintPath>$(BeatSaberDir)\Libs\Newtonsoft.Json.dll</HintPath>
Expand Down Expand Up @@ -147,6 +153,7 @@
<Compile Include="BeatmapRemixer.cs" />
<Compile Include="BuildConstants\Debug.cs" Condition="'$(Configuration)' == 'Debug'" />
<Compile Include="$(SolutionDir)Generated\BuildConstants\Release.cs" Link="BuildConstants\Release.cs" Condition="'$(Configuration)' == 'Release'" />
<Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="FloatComparison.cs" />
<Compile Include="DebugTools\Eval.cs" Condition="'$(Configuration)' == 'Debug'" />
<Compile Include="DebugTools\RemixVisualizer.cs" Condition="'$(Configuration)' == 'Debug'" />
Expand Down
4 changes: 2 additions & 2 deletions InfiniteBeatSaber/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyVersion("1.0.1")]
[assembly: AssemblyFileVersion("1.0.1")]
3 changes: 2 additions & 1 deletion InfiniteBeatSaber/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
"id": "InfiniteBeatSaber",
"name": "InfiniteBeatSaber",
"author": "rigdern",
"version": "1.0.0",
"version": "1.0.1",
"description": "Generates never-ending and ever changing levels. Inspired by the Infinite Jukebox.",
"gameVersion": "1.31.1",
"dependsOn": {
"BSIPA": "^4.3.0",
"CustomJSONData": "^2.5.1",
"SiraUtil": "^3.1.5"
}
}
5 changes: 4 additions & 1 deletion docs/manual-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ Here are the steps if you want to manually install the Infinite Beat Saber mod:

1. Download the latest version from the [releases page](https://github.com/rigdern/InfiniteBeatSaber/releases).
1. Unzip the release and drop `InfiniteBeatSaber.dll` into your Beat Saber's `Plugins/` folder. For example, mine is located at `C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Plugins`.
1. You'll also need to similarly manually install [SiraUtil](https://github.com/Auros/SiraUtil/releases) into your Beat Saber's `Plugins/` folder. Infinite Beat Saber relies on some functionality in `SiraUtil`.
1. You'll also need to similarly manually install these into your Beat Saber's `Plugins/` folder:
- [CustomJSONData](https://github.com/Aeroluna/CustomJSONData/releases)
- [SiraUtil](https://github.com/Auros/SiraUtil/releases)

You'll need to make sure that you have compatible versions of all of these things:
- Beat Saber
- Infinite Beat Saber mod
- CustomJSONData
- SiraUtil

To determine this, you can check `manifest.json` for the version of Infinite Beat Saber you downloaded.
Expand Down

0 comments on commit 81277f4

Please sign in to comment.