Skip to content

Commit d6bcac7

Browse files
Steam Rivals experimental feature (v1.2.0.0)
Changes: * Min/MaxLevelOfDetail config properties now have cached accessors (since LOD is intended to help improve performance). Because of this change, assigning the properties through the dictionary (instead of the getters/setters) must be avoided. * CreatePlayerDataReplay is now patched with a transpiler instead, to introduce the compound data setup before the final Init. New Feature: * Steam Rivals is a feature where other players can be marked as a *sort-of-friend* (rival). These players will be highlighted differently in the level select leaderboards menu, and will have different car visual styles, as chosen in the submenu options. This way you can spot your *true* rivals in a flock a ghosts from far away. * The submenu for Steam Rival options is available on page two of the Replay Intensifies settings menu. * The Steam Rivals mode setting as a whole is disabled by default, due to its experimental nature, and how it's mostly a tangent from the original goal of this mod. But it *needs* to exist in this mod to take advantage of the changes/additions to ghosts and rendering. New Options: * All options below are under the Steam Rivals submenu: * Option to enable Steam Rivals (disabled by default). * Option to enable/disable highlighting Steam Rivals in leaderboards. * Option to use Steam Replay car visual styles when racing ghosts. * Option to use Steam Replay car visual styles when in Replay Mode. * Option to always treat your own ghosts/replays as Steam Rivals. * Option to change the car visual style for Steam Rivals (just like for ghosts and Replay Mode cars). * Option to change the brightness level for Steam Rival cars (up to a max of 100000, capped just before black splotches would start appearing from extreme intensity).
1 parent 4571dae commit d6bcac7

16 files changed

Lines changed: 935 additions & 27 deletions

File tree

Distance.ReplayIntensifies/ConfigurationLogic.cs

Lines changed: 212 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Reactor.API.Configuration;
22
using System;
3-
using System.Collections;
43
using System.Collections.Generic;
4+
using System.Linq;
55
using UnityEngine;
66

77
namespace Distance.ReplayIntensifies
@@ -72,14 +72,22 @@ public CarLevelOfDetail.Type ReplayDetailType
7272
public CarLevelOfDetail.Level MaxLevelOfDetail
7373
{
7474
get => Get<CarLevelOfDetail.Level>(MaxDetailLevel_ID);
75-
set => Set(MaxDetailLevel_ID, value);
75+
set
76+
{
77+
Set(MaxDetailLevel_ID, value);
78+
this.MaxLevelOfDetailCached = value;
79+
}
7680
}
7781

7882
private const string MinDetailLevel_ID = "visual.min_level_of_detail";
7983
public CarLevelOfDetail.Level MinLevelOfDetail
8084
{
8185
get => Get<CarLevelOfDetail.Level>(MinDetailLevel_ID);
82-
set => Set(MinDetailLevel_ID, value);
86+
set
87+
{
88+
Set(MinDetailLevel_ID, value);
89+
this.MinLevelOfDetailCached = value;
90+
}
8391
}
8492

8593
private const string EnableUnrestrictedOpponentColors_ID = "visual.unrestricted_colors";
@@ -89,16 +97,93 @@ public bool EnableUnrestrictedOpponentColors
8997
set => Set(EnableUnrestrictedOpponentColors_ID, value);
9098
}
9199

100+
101+
private const string UseRivalStyleForGhosts_ID = "visual.use_rival_style_for_ghosts";
102+
public bool UseRivalStyleForGhosts
103+
{
104+
get => Get<bool>(UseRivalStyleForGhosts_ID);
105+
set => Set(UseRivalStyleForGhosts_ID, value);
106+
}
107+
108+
private const string UseRivalStyleForReplays_ID = "visual.use_rival_style_for_replays";
109+
public bool UseRivalStyleForReplays
110+
{
111+
get => Get<bool>(UseRivalStyleForReplays_ID);
112+
set => Set(UseRivalStyleForReplays_ID, value);
113+
}
114+
115+
private const string UseRivalStyleForSelf_ID = "visual.use_rival_style_for_self";
116+
public bool UseRivalStyleForSelf
117+
{
118+
get => Get<bool>(UseRivalStyleForSelf_ID);
119+
set => Set(UseRivalStyleForSelf_ID, value);
120+
}
121+
122+
private const string RivalBrightness_ID = "visual.rival_brightness";
123+
public float RivalBrightness
124+
{
125+
get => Get<float>(RivalBrightness_ID);
126+
set => Set(RivalBrightness_ID, value);
127+
}
128+
129+
private const string RivalOutline_ID = "visual.rival_outline";
130+
public bool RivalOutline
131+
{
132+
get => Get<bool>(RivalOutline_ID, true);
133+
set => Set(RivalOutline_ID, value);
134+
}
135+
136+
private const string RivalDetailType_ID = "visual.rival_detail_type";
137+
public CarLevelOfDetail.Type RivalDetailType
138+
{
139+
get => Get<CarLevelOfDetail.Type>(RivalDetailType_ID);
140+
set => Set(RivalDetailType_ID, value);
141+
}
142+
143+
private const string EnableSteamRivals_ID = "rivals.enabled";
144+
public bool EnableSteamRivals
145+
{
146+
// We can't be having this feature enabled for non-steam builds
147+
get => SteamworksManager.IsSteamBuild_ && Get<bool>(EnableSteamRivals_ID);
148+
set => Set(EnableSteamRivals_ID, value);
149+
}
150+
151+
private const string HighlightRivalsInLeaderboards_ID = "rivals.highlight_in_leaderboards";
152+
public bool HighlightRivalsInLeaderboards
153+
{
154+
get => Get<bool>(HighlightRivalsInLeaderboards_ID);
155+
set => Set(HighlightRivalsInLeaderboards_ID, value);
156+
}
157+
158+
private const string SteamRivals_ID = "rivals.steam_ids";
159+
public Dictionary<ulong, string> SteamRivals
160+
{
161+
get => Convert<Dictionary<ulong, string>>(SteamRivals_ID, new Dictionary<ulong, string>(), overwriteNull: true);
162+
private set => Set(SteamRivals_ID, value);
163+
}
164+
165+
#endregion
166+
167+
#region Cached
168+
169+
// Cached property values for faster accessing.
170+
public CarLevelOfDetail.Level MaxLevelOfDetailCached { get; private set; }
171+
public CarLevelOfDetail.Level MinLevelOfDetailCached { get; private set; }
172+
92173
#endregion
93174

94175
#region Helpers
95176

96-
public CarLevelOfDetail.Type GetCarDetailType(bool isGhost)
177+
public CarLevelOfDetail.Type GetCarDetailType(bool isGhost, bool isCarRival)
97178
{
179+
if (isCarRival)
180+
{
181+
return this.RivalDetailType;
182+
}
98183
return (isGhost) ? this.GhostDetailType : this.ReplayDetailType;
99184
}
100185

101-
public void SetCarDetailType(bool isGhost, CarLevelOfDetail.Type value)
186+
/*public void SetCarDetailType(bool isGhost, CarLevelOfDetail.Type value)
102187
{
103188
if (isGhost)
104189
{
@@ -108,14 +193,18 @@ public void SetCarDetailType(bool isGhost, CarLevelOfDetail.Type value)
108193
{
109194
this.ReplayDetailType = value;
110195
}
111-
}
196+
}*/
112197

113-
public bool GetCarOutline(bool isGhost)
198+
public bool GetCarOutline(bool isGhost, bool isCarRival)
114199
{
200+
if (isCarRival)
201+
{
202+
return this.RivalOutline;
203+
}
115204
return (isGhost) ? this.GhostOutline : this.ReplayOutline;
116205
}
117206

118-
public void SetCarOutline(bool isGhost, bool value)
207+
/*public void SetCarOutline(bool isGhost, bool value)
119208
{
120209
if (isGhost)
121210
{
@@ -125,6 +214,95 @@ public void SetCarOutline(bool isGhost, bool value)
125214
{
126215
this.ReplayOutline = value;
127216
}
217+
}*/
218+
219+
// Determines if this car should be displayed as a Steam Rival (which accounts for settings like 'Use rival style for ghosts/replays', etc.).
220+
public bool IsCarSteamRival(bool isGhost, long userID) => IsCarSteamRival(isGhost, unchecked((ulong)userID));
221+
222+
public bool IsCarSteamRival(bool isGhost, ulong userID)
223+
{
224+
if (this.EnableSteamRivals &&
225+
((isGhost && this.UseRivalStyleForGhosts) || (!isGhost && this.UseRivalStyleForReplays)))
226+
{
227+
return IsSteamRival(userID, false);
228+
}
229+
return false;
230+
}
231+
232+
#endregion
233+
234+
#region Steam Rivals
235+
236+
// The `excludeSelf` parameter exists in-case there are situations where we want to identify rivals for non-ghost/replay reasons.
237+
public bool IsSteamRival(long userID, bool excludeSelf = false) => IsSteamRival(unchecked((ulong)userID), excludeSelf);
238+
239+
public bool IsSteamRival(ulong userID, bool excludeSelf = false)
240+
{
241+
if (SteamworksManager.GetSteamID() == userID)
242+
{
243+
return !excludeSelf && this.UseRivalStyleForSelf; // SteamRivals ignores your own user ID in the list.
244+
}
245+
return this.SteamRivals.ContainsKey(userID);
246+
}
247+
248+
// For getting the name comment attached to a rival. These currently aren't used by the mod though.
249+
public bool TryGetSteamRival(long userID, out string nameComment) => TryGetSteamRival(unchecked((ulong)userID), out nameComment);
250+
251+
public bool TryGetSteamRival(ulong userID, out string nameComment)
252+
{
253+
return this.SteamRivals.TryGetValue(userID, out nameComment);
254+
}
255+
256+
public bool AddSteamRival(long userID, string nameComment, bool autoSave = true) => AddSteamRival(unchecked((ulong)userID), nameComment, autoSave);
257+
258+
public bool AddSteamRival(ulong userID, string nameComment, bool autoSave = true)
259+
{
260+
if (nameComment == null)
261+
{
262+
nameComment = string.Empty; // Default to empty string I guess? It doesn't really matter either way, but would be more user friendly.
263+
}
264+
265+
var steamRivals = this.SteamRivals;
266+
if (!steamRivals.ContainsKey(userID))
267+
{
268+
steamRivals[userID] = nameComment; // Name comment to make identifying users in Config.json easier.
269+
if (autoSave)
270+
{
271+
Save();
272+
}
273+
return true;
274+
}
275+
return false;
276+
}
277+
278+
public bool RemoveSteamRival(long userID, bool autoSave = true) => RemoveSteamRival(unchecked((ulong)userID), autoSave);
279+
280+
public bool RemoveSteamRival(ulong userID, bool autoSave = true)
281+
{
282+
if (this.SteamRivals.Remove(userID))
283+
{
284+
if (autoSave)
285+
{
286+
Save();
287+
}
288+
return true;
289+
}
290+
return false;
291+
}
292+
293+
// Shorthand function to reduce the time spent on lookup when a large number of userIDs are in a single collection.
294+
public int CountSteamRivals(IEnumerable<long> userIDs, bool countSelf = true)
295+
{
296+
return CountSteamRivals(userIDs.Select((userID) => unchecked((ulong)userID)), countSelf);
297+
}
298+
299+
public int CountSteamRivals(IEnumerable<ulong> userIDs, bool countSelf = true)
300+
{
301+
//var useRivalStyleForSelf = this.UseRivalStyleForSelf;
302+
ulong selfID = SteamworksManager.GetSteamID();
303+
var steamRivals = this.SteamRivals;
304+
305+
return userIDs.Count((userID) => (userID == selfID ? countSelf : steamRivals.ContainsKey(userID)));
128306
}
129307

130308
#endregion
@@ -153,10 +331,21 @@ public void Awake()
153331
Get(GhostDetailType_ID, CarLevelOfDetail.Type.Ghost);
154332
Get(ReplayOutline_ID, false);
155333
Get(ReplayDetailType_ID, CarLevelOfDetail.Type.Replay);
156-
Get(MaxDetailLevel_ID, CarLevelOfDetail.Level.InFocusFP);
157-
Get(MinDetailLevel_ID, CarLevelOfDetail.Level.Speck);
334+
this.MaxLevelOfDetailCached = Get(MaxDetailLevel_ID, CarLevelOfDetail.Level.InFocusFP);
335+
this.MinLevelOfDetailCached = Get(MinDetailLevel_ID, CarLevelOfDetail.Level.Speck);
158336
Get(EnableUnrestrictedOpponentColors_ID, false);
159337

338+
// Experimental (disabled by default)
339+
Get(EnableSteamRivals_ID, false);
340+
Convert(SteamRivals_ID, new Dictionary<ulong, string>(), overwriteNull: true);
341+
Get(HighlightRivalsInLeaderboards_ID, true);
342+
Get(UseRivalStyleForGhosts_ID, true);
343+
Get(UseRivalStyleForReplays_ID, false);
344+
Get(UseRivalStyleForSelf_ID, false);
345+
Get(RivalBrightness_ID, 1.0f);
346+
Get(RivalOutline_ID, true);
347+
Get(RivalDetailType_ID, CarLevelOfDetail.Type.Networked);
348+
160349
// Save settings, and any defaults that may have been added.
161350
Save();
162351
}
@@ -172,6 +361,19 @@ public void Set<T>(string key, T value)
172361
Save();
173362
}
174363

364+
public T Convert<T>(string key, T @default = default, bool overwriteNull = false)
365+
{
366+
// Assign the object back after conversion, this allows for deep nested settings
367+
// that can be preserved and updated without reassigning to the root property.
368+
var value = Config.GetOrCreate(key, @default);
369+
if (overwriteNull && value == null)
370+
{
371+
value = @default;
372+
}
373+
Config[key] = value;
374+
return value;
375+
}
376+
175377
public void Save()
176378
{
177379
Config?.Save();

Distance.ReplayIntensifies/Distance.ReplayIntensifies.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@
8787
</ItemGroup>
8888
<ItemGroup>
8989
<Compile Include="ConfigurationLogic.cs" />
90+
<Compile Include="Harmony\Assembly-CSharp\LevelSelectLeaderboardButton\OnDisplayedVirtual.cs" />
91+
<Compile Include="Harmony\Assembly-CSharp\LevelSelectLeaderboardMenu\Display.cs" />
92+
<Compile Include="Harmony\Assembly-CSharp\LevelSelectLeaderboardMenu\Update.cs" />
9093
<Compile Include="Harmony\Assembly-CSharp\PlayerDataReplay\CreatePlayerDataReplay.cs" />
94+
<Compile Include="Harmony\Assembly-CSharp\PlayerDataReplay\OnEventReplayOptionsMenuClosed.cs" />
95+
<Compile Include="Helpers\SteamworksHelper.cs" />
9196
<Compile Include="Scripts\PlayerDataReplayCompoundData.cs" />
9297
<Compile Include="Harmony\Assembly-CSharp\CarLevelOfDetail\SetLevelOfDetail.cs" />
9398
<Compile Include="Harmony\Assembly-CSharp\FinishMenuLogic\ShowOnlineLeaderboards.cs" />

Distance.ReplayIntensifies/Harmony/Assembly-CSharp/CarLevelOfDetail/SetLevelOfDetail.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal static bool Prefix(CarLevelOfDetail __instance, CarLevelOfDetail.Level
1818
{
1919
// Avoid clamping if the max is unchanged (InFocusFP), just in-case other mods mess with this...
2020
// NOTE: Highest LOD is lowest-value enum, so compare using less than.
21-
CarLevelOfDetail.Level maxLevel = Mod.Instance.Config.MaxLevelOfDetail;
21+
CarLevelOfDetail.Level maxLevel = Mod.Instance.Config.MaxLevelOfDetailCached;
2222

2323
// NOTE: Replay-type cars will NOT RENDER A SOLID BODY at Speck, so force the minimum to VeryFar.
2424
// Interestingly, networked cars WILL render a solid body at Speck.
@@ -34,7 +34,7 @@ internal static bool Prefix(CarLevelOfDetail __instance, CarLevelOfDetail.Level
3434
{
3535
// Avoid clamping if the min is unchanged (Speck), just in-case other mods mess with this...
3636
// MaxLevel has priority over MinLevel (when the range is invalid)
37-
CarLevelOfDetail.Level minLevel = Mod.Instance.Config.MinLevelOfDetail;
37+
CarLevelOfDetail.Level minLevel = Mod.Instance.Config.MinLevelOfDetailCached;
3838
if (newLevel > minLevel && minLevel != CarLevelOfDetail.Level.Speck)
3939
{
4040
newLevel = minLevel;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using HarmonyLib;
2+
3+
namespace Distance.ReplayIntensifies.Harmony
4+
{
5+
/// <summary>
6+
/// Patch to highlight rivals in the level select leaderboards menu.
7+
/// </summary>
8+
/// <remarks>
9+
/// Required For: Highlight Rivals in Leaderboards
10+
/// </remarks>
11+
[HarmonyPatch(typeof(LevelSelectLeaderboardButton), nameof(LevelSelectLeaderboardButton.OnDisplayedVirtual))]
12+
internal static class LevelSelectLeaderboardButton__OnDisplayedVirtual
13+
{
14+
[HarmonyPostfix]
15+
internal static void Postfix(LevelSelectLeaderboardButton __instance)
16+
{
17+
Mod.UpdateLeaderboardButtonColor(__instance, false);
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)