Skip to content

Commit af912e3

Browse files
Randomized Cars feature (v1.3.0.0)
Changes: * Changed description for Data Effect in Ghost Mode to note that it also effects the finish despawn effect. New Options: * Randomized Cars - Cars will be randomly assigned a car type and colors, allowing for more variety when racing ghosts. Randomized Cars: * Enable or disabled the Randomized Cars feature. * Change whether randomness is fixed, so that individual replays will use the same random values. * Add an extra randomness seed that's combined with Fixed Randomness (or Random By Placement) to change up what you'll normally see. * Change whether cars are randomized for local leaderboards replays. * Change whether cars are randomized for online leaderboards replays. * Change whether cars are randomized for Steam Rival replays (requires Steam Rivals to be enabled). * Change whether online replays using the Kickstarter backer car will also be randomized (randomization is disabled by default). * Change individual weights of vanilla cars, and weights for all custom cars, so that certain cars will be more likely to get chosen. * Weights for individual custom cars can be changed by adding entries to `random.car_chances` in `Settings/Config.json`. * Change how random cars are selected: * Off - Car types won't be randomized. * Car Types - Default behavior where duplicates are allowed. Choose cars based on individual weighted values. * Car Types (Cycle) - Cycle through all available car types before choosing duplicates. * Change whether car/color randomness is decided by placement order (otherwise it will be decided by replay, or random if Fixed Randomness is disabled). * Change how random car colors are selected: * Off - Car colors won't be randomized. * Color Presets - Default behavior where duplicates are allowed. Colors are chozen from your car color presets. * Color Presets (Cycle) - Cycle through all available color presets before choosing duplicates. * HSV - Colors are generated with random Hue/Saturation/Value channels. * RGB - Colors are generated with random Red/Green/Blue channels. * Default Colors - Cars will always use their own default color preset. Useful for custom cars that aren't intended to be recolored.
1 parent f5d7479 commit af912e3

18 files changed

+1717
-12
lines changed

Distance.ReplayIntensifies/ConfigurationLogic.cs

Lines changed: 409 additions & 1 deletion
Large diffs are not rendered by default.

Distance.ReplayIntensifies/Distance.ReplayIntensifies.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,19 @@
9494
<Compile Include="Harmony\Assembly-CSharp\PlayerDataReplay\get_CreateCarScreen_.cs" />
9595
<Compile Include="Harmony\Assembly-CSharp\PlayerDataReplay\InitCarVirtual.cs" />
9696
<Compile Include="Harmony\Assembly-CSharp\PlayerDataReplay\OnEventReplayOptionsMenuClosed.cs" />
97+
<Compile Include="Harmony\Assembly-CSharp\ReplayManager\AddOnlineReplaysToGroup.cs" />
98+
<Compile Include="Harmony\Assembly-CSharp\ReplayManager\AddReplayToGroup.cs" />
99+
<Compile Include="Harmony\Assembly-CSharp\ReplayManager\OnAllPlayerEventFinish.cs" />
100+
<Compile Include="Harmony\Assembly-CSharp\ReplayManager\OnEventModeStarted.cs" />
97101
<Compile Include="Harmony\Assembly-CSharp\ReplayManager\OnLoadedOnlineReplaysDownloadFinished.cs" />
102+
<Compile Include="Randomizer\RandomCarMethod.cs" />
103+
<Compile Include="Randomizer\RandomColorMethod.cs" />
104+
<Compile Include="Scripts\ReplayManagerCompoundData.cs" />
105+
<Compile Include="Helpers\Crc.cs" />
98106
<Compile Include="Helpers\SteamworksHelper.cs" />
107+
<Compile Include="Randomizer\RandomColorPreset.cs" />
108+
<Compile Include="Randomizer\RandomCarType.cs" />
109+
<Compile Include="Scripts\CarReplayDataCompoundData.cs" />
99110
<Compile Include="Scripts\PlayerDataReplayCompoundData.cs" />
100111
<Compile Include="Harmony\Assembly-CSharp\CarLevelOfDetail\SetLevelOfDetail.cs" />
101112
<Compile Include="Harmony\Assembly-CSharp\FinishMenuLogic\ShowOnlineLeaderboards.cs" />

Distance.ReplayIntensifies/Harmony/Assembly-CSharp/PlayerDataBase/Initialize.cs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
1-
using HarmonyLib;
2-
using System;
1+
using Distance.ReplayIntensifies.Scripts;
2+
using HarmonyLib;
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Reflection;
66
using System.Reflection.Emit;
7-
using UnityEngine;
87

98
namespace Distance.ReplayIntensifies.Harmony
109
{
1110
/// <summary>
1211
/// Patch to allow unclamped car colors for opponents (so others can show off their ultrabright car presets).
1312
/// This patch is needed because the <see cref="PlayerDataBase.ClampCarColors_"/> property seems to be inlined
1413
/// by the JIT.
14+
/// <para/>
15+
/// Also includes patch to initialized randomized car type and colors.
1516
/// </summary>
1617
/// <remarks>
17-
/// Required For: Unrestricted Opponent Car Colors.
18+
/// Required For: Unrestricted Opponent Car Colors, and Randomized Cars.
1819
/// </remarks>
1920
[HarmonyPatch(typeof(PlayerDataBase), nameof(PlayerDataBase.Initialize))]
2021
internal static class PlayerDataBase__Initialize
2122
{
2223
[HarmonyTranspiler]
2324
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
2425
{
25-
Mod.Instance.Logger.Info("Transpiling...");
26+
var codes = new List<CodeInstruction>(instructions);
27+
28+
Mod.Instance.Logger.Info("Transpiling... (1/3)");
2629
// VISUAL:
2730
// Replace call to PlayerDataBase.ClampCarColors_.get with Mod.GetClampCarColors
2831
//if (PlayerDataBase.ClampCarColors_)
2932
//{
3033
// data.carColors_.ClampColors();
3134
//}
3235

33-
var codes = new List<CodeInstruction>(instructions);
3436
for (int i = 0; i < codes.Count; i++)
3537
{
3638
// Find getter for: virtual bool PlayerDataBase.ClampCarColors_
@@ -46,7 +48,82 @@ internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruct
4648
break;
4749
}
4850
}
51+
52+
Mod.Instance.Logger.Info("Transpiling... (2/3)");
53+
// VISUAL:
54+
// Override call to assigning car type so we can hook random car selection.
55+
//this.UseCarWithName(data.carName_, null);
56+
// -to-
57+
//UseCarWithNameOverride_(this, data.carName_, null);
58+
59+
for (int i = 0; i < codes.Count; i++)
60+
{
61+
if (codes[i].opcode == OpCodes.Call && ((MethodInfo)codes[i].operand).Name == "UseCarWithName")
62+
{
63+
Mod.Instance.Logger.Info($"call UseCarWithName @ {i}");
64+
65+
// Replace: call UseCarWithName
66+
// With: call UseCarWithNameOverride_
67+
codes[i].opcode = OpCodes.Call;
68+
codes[i].operand = typeof(PlayerDataBase__Initialize).GetMethod(nameof(UseCarWithNameOverride_));
69+
70+
break;
71+
}
72+
}
73+
74+
Mod.Instance.Logger.Info("Transpiling... (3/3)");
75+
// VISUAL:
76+
// Override call to assigning car colors so we can hook random car selection.
77+
//this.SetOriginalColors(data.carColors_);
78+
// -to-
79+
//SetOriginalColorsOverride_(this, data.carColors_);
80+
81+
for (int i = 0; i < codes.Count; i++)
82+
{
83+
if (codes[i].opcode == OpCodes.Call && ((MethodInfo)codes[i].operand).Name == "SetOriginalColors")
84+
{
85+
Mod.Instance.Logger.Info($"call SetOriginalColors @ {i}");
86+
87+
// Replace: call SetOriginalColors
88+
// With: call SetOriginalColorsOverride_
89+
codes[i].opcode = OpCodes.Call;
90+
codes[i].operand = typeof(PlayerDataBase__Initialize).GetMethod(nameof(SetOriginalColorsOverride_));
91+
92+
break;
93+
}
94+
}
95+
4996
return codes.AsEnumerable();
5097
}
98+
99+
#region Helpers
100+
101+
public static bool UseCarWithNameOverride_(PlayerDataBase playerDataBase, string carName, Profile profile)
102+
{
103+
if (playerDataBase is PlayerDataReplay playerDataReplay)
104+
{
105+
var compoundData = playerDataReplay.GetComponent<PlayerDataReplayCompoundData>();
106+
if (compoundData && compoundData.IsRandomnessEnabled)
107+
{
108+
carName = compoundData.CarData.name_;
109+
}
110+
}
111+
return playerDataBase.UseCarWithName(carName, profile);
112+
}
113+
114+
public static void SetOriginalColorsOverride_(PlayerDataBase playerDataBase, CarColors carColors)
115+
{
116+
if (playerDataBase is PlayerDataReplay playerDataReplay)
117+
{
118+
var compoundData = playerDataReplay.GetComponent<PlayerDataReplayCompoundData>();
119+
if (compoundData && compoundData.IsRandomnessEnabled)
120+
{
121+
carColors = compoundData.CarData.colors_;
122+
}
123+
}
124+
playerDataBase.SetOriginalColors(carColors);
125+
}
126+
127+
#endregion
51128
}
52129
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using Distance.ReplayIntensifies.Scripts;
2+
using HarmonyLib;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
using UnityEngine;
8+
9+
namespace Distance.ReplayIntensifies.Harmony
10+
{
11+
/// <summary>
12+
/// Patch to mark replays that were loaded from online leaderboards.
13+
/// </summary>
14+
/// <remarks>
15+
/// Required For: Use Random Online Cars.
16+
/// </remarks>
17+
[HarmonyPatch(typeof(ReplayManager), nameof(ReplayManager.AddOnlineReplaysToGroup))]
18+
internal static class ReplayManager__AddOnlineReplaysToGroup
19+
{
20+
[HarmonyTranspiler]
21+
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
22+
{
23+
var codes = new List<CodeInstruction>(instructions);
24+
25+
Mod.Instance.Logger.Info("Transpiling...");
26+
// VISUAL:
27+
//this.AddReplayToGroup(carReplayData, group);
28+
// -to-
29+
//AddOnlineReplayToGroupOverride_(this, carReplayData, group);
30+
31+
for (int i = 0; i < codes.Count; i++)
32+
{
33+
if (codes[i].opcode == OpCodes.Call && ((MethodInfo)codes[i].operand).Name == "AddReplayToGroup")
34+
{
35+
Mod.Instance.Logger.Info($"call AddReplayToGroup @ {i}");
36+
37+
// Replace: call AddReplayToGroup
38+
// With: call AddOnlineReplayToGroupOverride_
39+
codes[i].opcode = OpCodes.Call;
40+
codes[i].operand = typeof(ReplayManager__AddOnlineReplaysToGroup).GetMethod(nameof(AddOnlineReplayToGroupOverride_));
41+
42+
break;
43+
}
44+
}
45+
46+
return codes.AsEnumerable();
47+
}
48+
49+
#region Helpers
50+
51+
public static void AddOnlineReplayToGroupOverride_(ReplayManager replayManager, CarReplayData carReplayData, GameObject group)
52+
{
53+
if (carReplayData)
54+
{
55+
var carCompoundData = carReplayData.gameObject.GetOrAddComponent<CarReplayDataCompoundData>();
56+
if (carCompoundData)
57+
{
58+
carCompoundData.IsOnline = true;
59+
}
60+
}
61+
62+
replayManager.AddReplayToGroup(carReplayData, group);
63+
}
64+
65+
#endregion
66+
}
67+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Distance.ReplayIntensifies.Scripts;
2+
using HarmonyLib;
3+
using UnityEngine;
4+
5+
namespace Distance.ReplayIntensifies.Harmony
6+
{
7+
/// <summary>
8+
/// Patch to mark replays that did not finish the level, and ensure compound data is always created.
9+
/// </summary>
10+
/// <remarks>
11+
/// Required For: Randomized Cars.
12+
/// </remarks>
13+
[HarmonyPatch(typeof(ReplayManager), nameof(ReplayManager.AddReplayToGroup))]
14+
internal static class ReplayManager__AddReplayToGroup
15+
{
16+
[HarmonyPostfix]
17+
internal static void Postfix(ReplayManager __instance, CarReplayData carReplayData, GameObject group)
18+
{
19+
if (carReplayData)
20+
{
21+
var carCompoundData = carReplayData.gameObject.GetOrAddComponent<CarReplayDataCompoundData>();
22+
if (carCompoundData)
23+
{
24+
carCompoundData.DidNotFinish = (group == __instance.didNotFinishReplays_);
25+
}
26+
}
27+
}
28+
}
29+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using Distance.ReplayIntensifies.Scripts;
2+
using HarmonyLib;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
using UnityEngine;
8+
9+
namespace Distance.ReplayIntensifies.Harmony
10+
{
11+
/// <summary>
12+
/// Patch to mark replays that were just created from the player's last run (so that we don't randomize them).
13+
/// </summary>
14+
/// <remarks>
15+
/// Required For: Randomized Cars.
16+
/// </remarks>
17+
[HarmonyPatch(typeof(ReplayManager), nameof(ReplayManager.OnAllPlayerEventFinish))]
18+
internal static class ReplayManager__OnAllPlayerEventFinish
19+
{
20+
[HarmonyTranspiler]
21+
internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
22+
{
23+
var codes = new List<CodeInstruction>(instructions);
24+
25+
Mod.Instance.Logger.Info("Transpiling...");
26+
// VISUAL:
27+
//this.AddReplayToGroup(component.FinishAndGetReplayData(data.finishData_), replayGroup);
28+
// -to-
29+
//AddMyPlayerReplayToGroupOverride_(this, component.FinishAndGetReplayData(data.finishData_), replayGroup);
30+
31+
for (int i = 0; i < codes.Count; i++)
32+
{
33+
if (codes[i].opcode == OpCodes.Call && ((MethodInfo)codes[i].operand).Name == "AddReplayToGroup")
34+
{
35+
Mod.Instance.Logger.Info($"call AddReplayToGroup @ {i}");
36+
37+
// Replace: call AddReplayToGroup
38+
// With: call AddMyPlayerReplayToGroupOverride_
39+
codes[i].opcode = OpCodes.Call;
40+
codes[i].operand = typeof(ReplayManager__OnAllPlayerEventFinish).GetMethod(nameof(AddMyPlayerReplayToGroupOverride_));
41+
42+
break;
43+
}
44+
}
45+
46+
return codes.AsEnumerable();
47+
}
48+
49+
#region Helpers
50+
51+
public static void AddMyPlayerReplayToGroupOverride_(ReplayManager replayManager, CarReplayData carReplayData, GameObject group)
52+
{
53+
if (carReplayData)
54+
{
55+
var carCompoundData = carReplayData.gameObject.GetOrAddComponent<CarReplayDataCompoundData>();
56+
if (carCompoundData)
57+
{
58+
carCompoundData.IsMyPlayer = true;
59+
}
60+
}
61+
62+
replayManager.AddReplayToGroup(carReplayData, group);
63+
}
64+
65+
#endregion
66+
}
67+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Distance.ReplayIntensifies.Scripts;
2+
using HarmonyLib;
3+
4+
namespace Distance.ReplayIntensifies.Harmony
5+
{
6+
[HarmonyPatch(typeof(ReplayManager), nameof(ReplayManager.OnEventModeStarted))]
7+
internal static class ReplayManager__OnEventModeStarted
8+
{
9+
[HarmonyPrefix]
10+
internal static void Prefix(ReplayManager __instance)
11+
{
12+
if (__instance.gm_.ModeID_ == GameModeID.LevelEditorPlay)
13+
{
14+
return;
15+
}
16+
17+
if (Mod.Instance.Config.EnableRandomizedCars)
18+
{
19+
// Create and setup compound data for managing randomization that affects all replay cars.
20+
ReplayManagerCompoundData.Create(__instance);
21+
}
22+
}
23+
24+
[HarmonyPostfix]
25+
internal static void Postfix(ReplayManager __instance)
26+
{
27+
// Cleanup for unused compound data.
28+
__instance.gameObject.RemoveComponent<ReplayManagerCompoundData>();
29+
}
30+
}
31+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Linq;
2+
using System.Text;
3+
4+
namespace Distance.ReplayIntensifies.Helpers
5+
{
6+
public static class Crc
7+
{
8+
private static readonly uint[] Crc32Table = Enumerable.Range(0, 256).Select(Calculate32).ToArray();
9+
10+
public const uint Initial32 = 0u;
11+
12+
private static uint Calculate32(int seed)
13+
{
14+
const uint poly = 0xEDB88320;
15+
uint value = unchecked((uint)seed);
16+
for (int i = 0; i < 8; i++)
17+
{
18+
value = ((value & 0x1) != 0) ? ((value >> 1) ^ poly) : (value >> 1);
19+
}
20+
return value;
21+
}
22+
23+
public static int Hash32(byte[] bytes, int init) => unchecked((int)Hash32(bytes, (uint)init));
24+
25+
public static uint Hash32(byte[] bytes, uint init = Initial32)
26+
{
27+
uint result = ~init;
28+
foreach (byte b in bytes)
29+
{
30+
result = (result >> 8) ^ Crc32Table[(byte)(result ^ b)];
31+
}
32+
return ~result;
33+
}
34+
35+
public static int Hash32(string text, int init) => unchecked((int)Hash32(text, (uint)init));
36+
37+
public static uint Hash32(string text, uint init = Initial32)
38+
{
39+
return Hash32(Encoding.UTF8.GetBytes(text), init);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)