using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.Mono;
using Effects;
using Game;
using Game.UnlockableContent;
using HarmonyLib;
using Menus.UI;
using Microsoft.CodeAnalysis;
using Player;
using Sounds.SplitScreenAudio;
using TMPro;
using UI.TabsSystem;
using UI.UnlockableContentGrid;
using UI.VectorGraphics;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Utils;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MoreCustomisations")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.0.0")]
[assembly: AssemblyInformationalVersion("0.5.0+66c6863e256e719fb60338ff72cca19afbcaaaa4")]
[assembly: AssemblyProduct("MoreCustomisations")]
[assembly: AssemblyTitle("MoreCustomisations")]
[assembly: AssemblyVersion("0.5.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace MoreCustomisations
{
internal static class Art
{
public static Texture2D Load(string file)
{
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
using Stream stream = typeof(Art).Assembly.GetManifestResourceStream(file);
if (stream == null)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(39, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Image '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(file);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' isn't embedded; is it in art/?");
}
log.LogWarning(val);
return null;
}
byte[] array = new byte[stream.Length];
stream.Read(array, 0, array.Length);
return Decode(array, file);
}
public static Texture2D LoadFile(string path)
{
if (!File.Exists(path))
{
return null;
}
return Decode(File.ReadAllBytes(path), Path.GetFileName(path));
}
private static Texture2D Decode(byte[] bytes, string name)
{
//IL_0004: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Expected O, but got Unknown
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Expected O, but got Unknown
Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
{
name = name,
wrapMode = (TextureWrapMode)1
};
if (ImageConversion.LoadImage(val, bytes))
{
return val;
}
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(30, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Image '");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(name);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' isn't a readable PNG.");
}
log.LogWarning(val2);
return null;
}
}
internal sealed class CarData
{
public string Name;
public List<CarSlot> Slots;
public List<CarPiece> Pieces;
}
internal sealed class CarSlot
{
public string Name;
public float R;
public float G;
public float B;
}
internal sealed class CarPiece
{
public string Name;
public List<CarMesh> Meshes;
}
internal sealed class CarMesh
{
public int Slot;
public float[] Positions;
public float[] Normals;
public int[] Triangles;
}
internal static class CarFile
{
public static readonly string[] Pieces = new string[9] { "Body", "Hood", "DoorLeft", "DoorRight", "Hatch", "WheelFL", "WheelFR", "WheelBL", "WheelBR" };
public static readonly string[] Required = new string[5] { "Body", "WheelFL", "WheelFR", "WheelBL", "WheelBR" };
private const string Magic = "MCCAR";
private const int Version = 1;
private const int MaxBytes = 4194304;
private const int MaxTriangles = 20000;
private const int MaxVertices = 65535;
public static byte[] Encode(CarData car)
{
using MemoryStream memoryStream = new MemoryStream();
using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8))
{
binaryWriter.Write(Encoding.ASCII.GetBytes("MCCAR"));
binaryWriter.Write(1);
WriteString(binaryWriter, car.Name);
binaryWriter.Write(car.Slots.Count);
foreach (CarSlot slot in car.Slots)
{
WriteString(binaryWriter, slot.Name);
binaryWriter.Write(slot.R);
binaryWriter.Write(slot.G);
binaryWriter.Write(slot.B);
}
binaryWriter.Write(car.Pieces.Count);
foreach (CarPiece piece in car.Pieces)
{
WriteString(binaryWriter, piece.Name);
binaryWriter.Write(piece.Meshes.Count);
foreach (CarMesh mesh in piece.Meshes)
{
binaryWriter.Write(mesh.Slot);
int num = mesh.Positions.Length / 3;
binaryWriter.Write(num);
for (int i = 0; i < num; i++)
{
for (int j = 0; j < 3; j++)
{
binaryWriter.Write(mesh.Positions[i * 3 + j]);
}
for (int k = 0; k < 3; k++)
{
binaryWriter.Write(mesh.Normals[i * 3 + k]);
}
}
binaryWriter.Write(mesh.Triangles.Length);
int[] triangles = mesh.Triangles;
foreach (int value in triangles)
{
binaryWriter.Write(value);
}
}
}
}
return memoryStream.ToArray();
}
public static CarData Decode(byte[] bytes, out string error)
{
try
{
error = null;
CarData carData = Read(bytes);
error = Problem(carData);
return (error == null) ? carData : null;
}
catch (InvalidDataException ex)
{
error = ex.Message;
return null;
}
catch (Exception ex2) when (ex2 is EndOfStreamException || ex2 is IOException || ex2 is ArgumentException)
{
error = "truncated or malformed (" + ex2.Message + ")";
return null;
}
}
private static CarData Read(byte[] bytes)
{
if (bytes == null || bytes.Length > 4194304)
{
throw new InvalidDataException("missing or over 4 MB");
}
using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes), Encoding.UTF8);
if (Encoding.ASCII.GetString(binaryReader.ReadBytes("MCCAR".Length)) != "MCCAR")
{
throw new InvalidDataException("not a car file");
}
int num = binaryReader.ReadInt32();
if (num != 1)
{
throw new InvalidDataException($"version {num}, expected {1}");
}
CarData carData = new CarData
{
Name = ReadString(binaryReader),
Slots = new List<CarSlot>(),
Pieces = new List<CarPiece>()
};
int num2 = Count(binaryReader, 1, 16, "slots");
for (int i = 0; i < num2; i++)
{
carData.Slots.Add(new CarSlot
{
Name = ReadString(binaryReader),
R = ReadFloat(binaryReader),
G = ReadFloat(binaryReader),
B = ReadFloat(binaryReader)
});
}
int num3 = Count(binaryReader, 1, 16, "pieces");
int num4 = 0;
for (int j = 0; j < num3; j++)
{
CarPiece carPiece = new CarPiece
{
Name = ReadString(binaryReader),
Meshes = new List<CarMesh>()
};
int num5 = Count(binaryReader, 1, 16, "meshes in " + carPiece.Name);
for (int k = 0; k < num5; k++)
{
int num6 = binaryReader.ReadInt32();
if (num6 < 0 || num6 >= num2)
{
throw new InvalidDataException($"{carPiece.Name}: slot {num6} out of range");
}
int num7 = Count(binaryReader, 1, 65535, "vertices in " + carPiece.Name);
CarMesh carMesh = new CarMesh
{
Slot = num6,
Positions = new float[num7 * 3],
Normals = new float[num7 * 3]
};
for (int l = 0; l < num7; l++)
{
for (int m = 0; m < 3; m++)
{
carMesh.Positions[l * 3 + m] = ReadFloat(binaryReader);
}
for (int n = 0; n < 3; n++)
{
carMesh.Normals[l * 3 + n] = ReadFloat(binaryReader);
}
}
int num8 = Count(binaryReader, 3, 60000, "indices in " + carPiece.Name);
if (num8 % 3 != 0)
{
throw new InvalidDataException($"{carPiece.Name}: {num8} indices isn't whole triangles");
}
num4 += num8 / 3;
if (num4 > 20000)
{
throw new InvalidDataException($"over {20000} triangles");
}
carMesh.Triangles = new int[num8];
for (int num9 = 0; num9 < num8; num9++)
{
int num10 = binaryReader.ReadInt32();
if (num10 < 0 || num10 >= num7)
{
throw new InvalidDataException($"{carPiece.Name}: index {num10} out of range");
}
carMesh.Triangles[num9] = num10;
}
carPiece.Meshes.Add(carMesh);
}
carData.Pieces.Add(carPiece);
}
if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length)
{
throw new InvalidDataException("bytes left over");
}
return carData;
}
private static string Problem(CarData car)
{
if (string.IsNullOrEmpty(car.Name) || car.Name.Length > 64)
{
return "name must be 1 to 64 characters";
}
if (car.Slots.Any((CarSlot s) => string.IsNullOrEmpty(s.Name)))
{
return "a slot has no name";
}
List<string> names = car.Pieces.Select((CarPiece p) => p.Name).ToList();
string text = names.FirstOrDefault((string n) => !Pieces.Contains(n));
if (text != null)
{
return "unknown piece '" + text + "' (pieces: " + string.Join(", ", Pieces) + ")";
}
IGrouping<string, string> grouping = (from n in names
group n by n).FirstOrDefault((IGrouping<string, string> g) => g.Count() > 1);
if (grouping != null)
{
return "piece '" + grouping.Key + "' appears twice";
}
string text2 = Required.FirstOrDefault((string n) => !names.Contains(n));
if (text2 == null)
{
return null;
}
return "missing required piece '" + text2 + "'";
}
private static int Count(BinaryReader r, int min, int max, string what)
{
int num = r.ReadInt32();
if (num < min || num > max)
{
throw new InvalidDataException($"{num} {what}, expected {min} to {max}");
}
return num;
}
private static float ReadFloat(BinaryReader r)
{
float num = r.ReadSingle();
if (float.IsNaN(num) || float.IsInfinity(num))
{
throw new InvalidDataException("non-finite number");
}
return num;
}
private static string ReadString(BinaryReader r)
{
int num = Count(r, 0, 256, "string bytes");
byte[] array = r.ReadBytes(num);
if (array.Length != num)
{
throw new EndOfStreamException();
}
return Encoding.UTF8.GetString(array);
}
private static void WriteString(BinaryWriter w, string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
w.Write(bytes.Length);
w.Write(bytes);
}
}
internal static class CarPatches
{
private static readonly Dictionary<UnlockablePlayerCustomizationSelector, PlayerInLobby> Lobbies = new Dictionary<UnlockablePlayerCustomizationSelector, PlayerInLobby>();
public static CustomTab CreateTab()
{
Cars.Choices.Changed += CarView.RefreshAll;
return new CustomTab("Car", "TabCar.png", Cars.Choices, () => Cars.All, delegate(PlayerData player, string car)
{
Cars.Choices.SetLocal(player, car);
}, delegate(UnlockablePlayerCustomizationSelector selector, PlayerData player, string car)
{
Lobby(selector)?.Show(car);
}, delegate(UnlockablePlayerCustomizationSelector selector, PlayerData player)
{
Lobby(selector)?.Refresh();
});
}
private static CarView Lobby(UnlockablePlayerCustomizationSelector selector)
{
if (!Lobbies.TryGetValue(selector, out var value) || (Object)(object)value == (Object)null)
{
PlayerInLobby[] array = Object.FindObjectsOfType<PlayerInLobby>();
foreach (PlayerInLobby val in array)
{
if ((Object)(object)val.customizationSelector == (Object)(object)selector)
{
value = (Lobbies[selector] = val);
}
}
}
if (!((Object)(object)value != (Object)null))
{
return null;
}
return CarView.ForLobby(value);
}
[HarmonyPatch(typeof(CarEffects), "SetAllContent", new Type[] { typeof(PlayerData) })]
[HarmonyPostfix]
private static void DressRaceCar(CarEffects __instance, PlayerData playerData)
{
Safely(delegate
{
CarView.ForRace(__instance, playerData)?.Refresh();
});
}
[HarmonyPatch(typeof(PlayerInLobby), "SetAllContent")]
[HarmonyPostfix]
private static void DressLobbyCar(PlayerInLobby __instance)
{
Safely(delegate
{
CarView.ForLobby(__instance)?.Refresh();
});
}
[HarmonyPatch(typeof(CarSkin), "Add", new Type[]
{
typeof(CarEffects),
typeof(int)
})]
[HarmonyPostfix]
private static void HatOnRaceCar(CarSkin __instance)
{
Rehide(__instance);
}
[HarmonyPatch(typeof(CarSkin), "Add", new Type[]
{
typeof(CarSkinPartsReferences),
typeof(int)
})]
[HarmonyPostfix]
private static void HatOnLobbyCar(CarSkin __instance)
{
Rehide(__instance);
}
private static void Rehide(CarSkin hat)
{
Safely(delegate
{
if ((Object)(object)hat != (Object)null)
{
((Component)hat).GetComponentInParent<CarView>()?.Rehide();
}
});
}
private static void Safely(Action action)
{
try
{
action();
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Car swap failed; the car stays as it was. " + ex));
}
}
}
internal static class Cars
{
public const string Stock = "Stock";
public static readonly Choices Choices = new Choices("Car", "Cars", "Stock");
private static List<CarData> _loaded;
private static List<ChoiceContent> _all;
private static Texture2D _defaultIcon;
private static string Folder => Path.Combine(Path.GetDirectoryName(typeof(Cars).Assembly.Location), "cars");
public static IReadOnlyList<CarData> Loaded => _loaded ?? (_loaded = Load());
public static IReadOnlyList<ChoiceContent> All => _all ?? (_all = (from n in new string[1] { "Stock" }.Concat(Loaded.Select((CarData c) => c.Name))
select ChoiceContent.Create("Car", n, Icon(n))).ToList());
public static CarData Get(string name)
{
return Loaded.FirstOrDefault((CarData c) => c.Name == name);
}
private static Texture2D Icon(string name)
{
return ((name != "Stock") ? Art.LoadFile(Path.Combine(Folder, name + ".png")) : null) ?? _defaultIcon ?? (_defaultIcon = Art.Load("TabCar.png"));
}
private static List<CarData> Load()
{
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Expected O, but got Unknown
//IL_0124: Unknown result type (might be due to invalid IL or missing references)
//IL_012b: Expected O, but got Unknown
//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
//IL_01af: Expected O, but got Unknown
List<CarData> list = new List<CarData>();
if (!Directory.Exists(Folder))
{
Plugin.Log.LogInfo((object)"No cars folder next to the DLL; only the stock car is available.");
return list;
}
bool flag = default(bool);
foreach (string item in Directory.GetFiles(Folder, "*.car").OrderBy<string, string>((string f) => f, StringComparer.Ordinal))
{
CarData car;
string error;
try
{
car = CarFile.Decode(File.ReadAllBytes(item), out error);
}
catch (IOException ex)
{
car = null;
error = ex.Message;
}
if (car == null)
{
ManualLogSource log = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(14, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Skipped car ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Path.GetFileName(item));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(error);
}
log.LogWarning(val);
}
else if (car.Name == "Stock" || list.Any((CarData c) => c.Name == car.Name))
{
ManualLogSource log2 = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(35, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Skipped car ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Path.GetFileName(item));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": the name '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(car.Name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' is taken.");
}
log2.LogWarning(val);
}
else
{
list.Add(car);
}
}
ManualLogSource log3 = Plugin.Log;
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(13, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Cars loaded: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>((list.Count > 0) ? string.Join(", ", list.Select((CarData c) => c.Name)) : "none");
}
log3.LogInfo(val2);
return list;
}
}
internal sealed class CarView : MonoBehaviour
{
private sealed class LiftingHatch : MonoBehaviour
{
private const float LiftPerSwing = 0.55f;
private Transform _door;
private Quaternion _doorRest;
private Quaternion _rest;
public static Transform Create(Transform door, CarPiece piece)
{
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Expected O, but got Unknown
//IL_00da: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Unknown result type (might be due to invalid IL or missing references)
//IL_0132: Unknown result type (might be due to invalid IL or missing references)
//IL_0137: Unknown result type (might be due to invalid IL or missing references)
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
//IL_015b: Unknown result type (might be due to invalid IL or missing references)
//IL_0161: Unknown result type (might be due to invalid IL or missing references)
//IL_0163: Unknown result type (might be due to invalid IL or missing references)
float num = float.MaxValue;
float num2 = float.MinValue;
float num3 = 0f;
int num4 = 0;
foreach (CarMesh mesh in piece.Meshes)
{
for (int i = 0; i < mesh.Positions.Length; i += 3)
{
num3 += mesh.Positions[i];
num = Mathf.Min(num, mesh.Positions[i + 1]);
num2 = Mathf.Max(num2, mesh.Positions[i + 2]);
num4++;
}
}
Vector3 val = default(Vector3);
((Vector3)(ref val))..ctor(num3 / (float)num4, num, num2);
GameObject val2 = new GameObject("MoreCustomisations_Car_HatchHinge");
val2.layer = ((Component)door).gameObject.layer;
val2.transform.SetParent(door.parent, false);
val2.transform.localPosition = door.localPosition + door.localRotation * val;
val2.transform.localRotation = door.localRotation;
LiftingHatch liftingHatch = val2.AddComponent<LiftingHatch>();
liftingHatch._door = door;
liftingHatch._doorRest = door.localRotation;
liftingHatch._rest = door.localRotation;
GameObject val3 = new GameObject("MoreCustomisations_Car_HatchLid")
{
layer = ((Component)door).gameObject.layer
};
val3.transform.SetParent(val2.transform, false);
val3.transform.localPosition = -val;
return val3.transform;
}
private void LateUpdate()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_door == (Object)null))
{
float num = Quaternion.Angle(_doorRest, _door.localRotation);
((Component)this).transform.localRotation = _rest * Quaternion.Euler(num * 0.55f, 0f, 0f);
}
}
}
private const string PiecePrefix = "MoreCustomisations_Car_";
private static readonly string[] KeepUnder = new string[3] { "Boost", "SurfaceEffects", "Particles" };
private static readonly HashSet<CarView> Live = new HashSet<CarView>();
private static readonly Dictionary<CarMesh, Mesh> Meshes = new Dictionary<CarMesh, Mesh>();
private static readonly Dictionary<(CarData, int), Material> Materials = new Dictionary<(CarData, int), Material>();
private static Material _template;
private Transform _root;
private Transform[] _targets;
private Func<PlayerData> _player;
private bool _lobby;
private CarData _shown;
private readonly List<GameObject> _pieces = new List<GameObject>();
private readonly List<Renderer> _hidden = new List<Renderer>();
public static CarView ForRace(CarEffects effects, PlayerData player)
{
return For(((Object)(object)effects.hood != (Object)null) ? effects.hood.parent : effects.body, (Transform[])(object)new Transform[8] { effects.hood, effects.doorLeft, effects.doorRight, effects.doorBack, effects.wheelFL, effects.wheelFR, effects.wheelBL, effects.wheelBR }, () => player, lobby: false);
}
public static CarView ForLobby(PlayerInLobby lobby)
{
CarSkinPartsReferences carRendererAccessoryHolder = lobby.carRendererAccessoryHolder;
if ((Object)(object)carRendererAccessoryHolder == (Object)null)
{
return null;
}
return For(((Object)(object)carRendererAccessoryHolder.hood != (Object)null) ? carRendererAccessoryHolder.hood.parent : (((Object)(object)lobby.carBody != (Object)null) ? lobby.carBody.transform : null), (Transform[])(object)new Transform[8] { carRendererAccessoryHolder.hood, carRendererAccessoryHolder.doorLeft, carRendererAccessoryHolder.doorRight, carRendererAccessoryHolder.doorBack, carRendererAccessoryHolder.wheelFL, carRendererAccessoryHolder.wheelFR, carRendererAccessoryHolder.wheelBL, carRendererAccessoryHolder.wheelBR }, () => Traverse.Create((object)lobby).Field("_player").GetValue<PlayerData>(), lobby: true);
}
private static CarView For(Transform body, Transform[] rest, Func<PlayerData> player, bool lobby)
{
Transform val = rest.Skip(4).FirstOrDefault((Func<Transform, bool>)((Transform w) => (Object)(object)w != (Object)null));
if ((Object)(object)body == (Object)null || (Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"A car without a body or wheels; leaving it stock.");
return null;
}
CarView carView = ((Component)body).GetComponent<CarView>() ?? ((Component)body).gameObject.AddComponent<CarView>();
carView._root = CommonParent(body, val);
carView._targets = ((IEnumerable<Transform>)(object)new Transform[1] { body }).Concat(rest).ToArray();
carView._player = player;
carView._lobby = lobby;
Live.Add(carView);
return carView;
}
public void Refresh()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
PlayerData val = _player();
if (!((Object)(object)val == (Object)null))
{
Show((_lobby && (int)val.type == 0) ? Cars.Choices.Saved(val) : Cars.Choices.Get(val));
}
}
public void Show(string name)
{
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Expected O, but got Unknown
CarData carData = Cars.Get(name);
try
{
if (carData != _shown)
{
Clear();
if (carData != null)
{
Build(carData);
}
_shown = carData;
}
if (carData != null)
{
Hide();
}
}
catch (Exception ex)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(45, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Couldn't show car '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'; showing the stock car. ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
Clear();
_shown = null;
}
}
public void Rehide()
{
if (_shown != null)
{
Hide();
}
}
public static void RefreshAll()
{
foreach (CarView item in Live.ToList())
{
if ((Object)(object)item == (Object)null)
{
Live.Remove(item);
}
else
{
item.Refresh();
}
}
}
private void Build(CarData car)
{
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Expected O, but got Unknown
//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0105: Expected O, but got Unknown
Material template = Template();
int i;
bool flag = default(bool);
for (i = 0; i < CarFile.Pieces.Length; i++)
{
CarPiece carPiece = car.Pieces.FirstOrDefault((CarPiece p) => p.Name == CarFile.Pieces[i]);
Transform val = _targets[i];
if (carPiece == null)
{
continue;
}
if ((Object)(object)val == (Object)null)
{
ManualLogSource log = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(45, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(car.Name);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": this car has no ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(carPiece.Name);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" part to carry it; skipped.");
}
log.LogWarning(val2);
continue;
}
Transform val3 = val;
if (carPiece.Name == "Hatch")
{
val3 = LiftingHatch.Create(val, carPiece);
_pieces.Add(((Component)val3.parent).gameObject);
}
foreach (CarMesh mesh in carPiece.Meshes)
{
GameObject val4 = new GameObject("MoreCustomisations_Car_" + carPiece.Name);
val4.layer = ((Component)val).gameObject.layer;
val4.transform.SetParent(val3, false);
val4.AddComponent<MeshFilter>().sharedMesh = MeshFor(car, mesh);
((Renderer)val4.AddComponent<MeshRenderer>()).sharedMaterial = MaterialFor(car, mesh.Slot, template);
_pieces.Add(val4);
}
}
}
private void Hide()
{
MeshRenderer[] componentsInChildren = ((Component)_root).GetComponentsInChildren<MeshRenderer>(true);
foreach (MeshRenderer val in componentsInChildren)
{
if (((Renderer)val).enabled && !((Object)val).name.StartsWith("MoreCustomisations_Car_") && !Kept(((Component)val).transform))
{
((Renderer)val).enabled = false;
_hidden.Add((Renderer)(object)val);
}
}
}
private void Clear()
{
foreach (GameObject piece in _pieces)
{
if ((Object)(object)piece != (Object)null)
{
Object.Destroy((Object)(object)piece);
}
}
_pieces.Clear();
foreach (Renderer item in _hidden)
{
if ((Object)(object)item != (Object)null)
{
item.enabled = true;
}
}
_hidden.Clear();
}
private bool Kept(Transform t)
{
while ((Object)(object)t != (Object)null && (Object)(object)t != (Object)(object)_root)
{
if (KeepUnder.Any((string k) => ((Object)t).name.Contains(k)))
{
return true;
}
t = t.parent;
}
return false;
}
private Material Template()
{
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
//IL_00e8: Expected O, but got Unknown
if ((Object)(object)_template != (Object)null)
{
return _template;
}
List<Material> source = (from m in ((Component)_root).GetComponentsInChildren<MeshRenderer>(true).SelectMany((MeshRenderer r) => ((Renderer)r).sharedMaterials)
where (Object)(object)m != (Object)null
select m).ToList();
_template = ((IEnumerable<Material>)source).FirstOrDefault((Func<Material, bool>)((Material m) => m.HasProperty("_RampTint") && m.HasProperty("_Color"))) ?? ((IEnumerable<Material>)source).FirstOrDefault((Func<Material, bool>)((Material m) => m.HasProperty("_Color")));
if ((Object)(object)_template == (Object)null)
{
throw new InvalidOperationException("no stock material with a _Color to copy");
}
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(25, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Car materials copy '");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((Object)_template).name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((Object)_template.shader).name);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(").");
}
log.LogInfo(val);
return _template;
}
private static Material MaterialFor(CarData car, int slot, Material template)
{
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0073: Expected O, but got Unknown
//IL_0079: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
if (Materials.TryGetValue((car, slot), out var value) && (Object)(object)value != (Object)null)
{
return value;
}
CarSlot carSlot = car.Slots[slot];
Color val = default(Color);
((Color)(ref val))..ctor(carSlot.R, carSlot.G, carSlot.B, 1f);
value = new Material(template)
{
name = "MoreCustomisations_" + car.Name + "_" + carSlot.Name
};
value.SetColor("_Color", val);
if (value.HasProperty("_RampTint"))
{
value.SetColor("_RampTint", new Color(carSlot.R * 0.75f, carSlot.G * 0.75f, carSlot.B * 0.75f, 1f));
}
return Materials[(car, slot)] = value;
}
private static Mesh MeshFor(CarData car, CarMesh source)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Expected O, but got Unknown
if (Meshes.TryGetValue(source, out var value) && (Object)(object)value != (Object)null)
{
return value;
}
value = new Mesh
{
name = "MoreCustomisations_" + car.Name
};
value.vertices = Vectors(source.Positions);
value.normals = Vectors(source.Normals);
value.triangles = source.Triangles;
return Meshes[source] = value;
}
private static Vector3[] Vectors(float[] xyz)
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
Vector3[] array = (Vector3[])(object)new Vector3[xyz.Length / 3];
for (int i = 0; i < array.Length; i++)
{
array[i] = new Vector3(xyz[i * 3], xyz[i * 3 + 1], xyz[i * 3 + 2]);
}
return array;
}
private static Transform CommonParent(Transform a, Transform b)
{
HashSet<Transform> hashSet = new HashSet<Transform>();
Transform val = a;
while ((Object)(object)val != (Object)null)
{
hashSet.Add(val);
val = val.parent;
}
Transform val2 = b;
while ((Object)(object)val2 != (Object)null)
{
if (hashSet.Contains(val2))
{
return val2;
}
val2 = val2.parent;
}
return a.root;
}
private void OnDestroy()
{
Live.Remove(this);
}
}
public class ChoiceContent : UnlockableContent
{
public string Kind;
public string ChoiceName;
public override UnlockableContentType Type()
{
return (UnlockableContentType)1;
}
public static ChoiceContent Create(string kind, string choiceName, Texture2D preview)
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
ChoiceContent choiceContent = ScriptableObject.CreateInstance<ChoiceContent>();
((Object)choiceContent).name = "MoreCustomisations_" + kind + "_" + choiceName;
((Object)choiceContent).hideFlags = (HideFlags)32;
choiceContent.Kind = kind;
choiceContent.ChoiceName = choiceName;
((UnlockableContent)choiceContent).unlockedByDefault = true;
using (MD5 mD = MD5.Create())
{
((UnlockableContent)choiceContent).id = new SerializableGuid(new Guid(mD.ComputeHash(Encoding.UTF8.GetBytes("MoreCustomisations." + kind + "." + choiceName))));
}
((UnlockableContent)choiceContent).preview = preview;
return choiceContent;
}
}
internal sealed class Choices
{
private readonly string _kind;
private readonly string _section;
private readonly string _default;
private readonly string _messageName;
private readonly Func<PlayerData, string, string> _resolve;
private readonly Dictionary<ulong, string> _table = new Dictionary<ulong, string>();
private readonly Dictionary<int, ConfigEntry<string>> _saved = new Dictionary<int, ConfigEntry<string>>();
private readonly Dictionary<int, PlayerData> _localPlayers = new Dictionary<int, PlayerData>();
private CustomMessagingManager _registeredOn;
private bool _toldAboutHost;
public IReadOnlyCollection<PlayerData> LocalPlayers => _localPlayers.Values;
public event Action Changed;
public Choices(string kind, string section, string defaultValue, Func<PlayerData, string, string> resolve = null)
{
_kind = kind;
_section = section;
_default = defaultValue;
_messageName = "MoreCustomisations." + section;
_resolve = resolve ?? ((Func<PlayerData, string, string>)((PlayerData _, string saved) => saved));
}
public string Get(PlayerData player)
{
if (!((Object)(object)player != (Object)null) || !_table.TryGetValue(((NetworkBehaviour)player).NetworkObjectId, out var value))
{
return _default;
}
return value;
}
public string Saved(PlayerData player)
{
return Entry(player).Value;
}
public void SetLocal(PlayerData player, string value)
{
Entry(player).Value = value;
Announce(player);
}
public void Announce(PlayerData player)
{
//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Expected O, but got Unknown
NetworkManager singleton = NetworkManager.Singleton;
if ((Object)(object)singleton == (Object)null || !singleton.IsListening || (Object)(object)player == (Object)null || !((NetworkBehaviour)player).IsSpawned)
{
return;
}
_localPlayers[player.LocalIndex] = player;
Dictionary<ulong, string> dictionary = new Dictionary<ulong, string> { [((NetworkBehaviour)player).NetworkObjectId] = _resolve(player, Entry(player).Value) };
if (singleton.IsServer)
{
Merge(dictionary);
Broadcast();
return;
}
Send(ChoiceTable.Encode(dictionary), 0uL);
if (!_toldAboutHost)
{
_toldAboutHost = true;
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(91, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Sent ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(_kind.ToLowerInvariant());
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" choice to the host. If the host doesn't have MoreCustomisations, everyone gets stock.");
}
log.LogInfo(val);
}
}
public void Tick()
{
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Expected O, but got Unknown
NetworkManager singleton = NetworkManager.Singleton;
CustomMessagingManager val = (((Object)(object)singleton != (Object)null) ? singleton.CustomMessagingManager : null);
if (val != _registeredOn)
{
_registeredOn = val;
_table.Clear();
_localPlayers.Clear();
_toldAboutHost = false;
if (val != null)
{
val.RegisterNamedMessageHandler(_messageName, new HandleNamedMessageDelegate(OnMessage));
}
}
}
private ConfigEntry<string> Entry(PlayerData player)
{
int num = player.LocalIndex + 1;
if (!_saved.TryGetValue(num, out var value))
{
value = (_saved[num] = Plugin.Cfg.Bind<string>(_section, $"Player{num}{_kind}", _default, $"{_kind} used by local player {num}."));
}
return value;
}
private void OnMessage(ulong sender, FastBufferReader reader)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: Expected O, but got Unknown
Dictionary<ulong, string> dictionary;
bool flag = default(bool);
try
{
int num = default(int);
((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
if (num < 0 || num > 16384)
{
throw new OverflowException("length " + num);
}
byte[] bytes = new byte[num];
((FastBufferReader)(ref reader)).ReadBytesSafe(ref bytes, num, 0);
dictionary = ChoiceTable.Decode(bytes);
}
catch (OverflowException ex)
{
ManualLogSource log = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(43, 3, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Ignored a malformed ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(_kind.ToLowerInvariant());
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" message from client ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<ulong>(sender);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
}
log.LogWarning(val);
return;
}
if (dictionary == null)
{
ManualLogSource log2 = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(42, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Ignored a malformed ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(_kind.ToLowerInvariant());
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" message from client ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<ulong>(sender);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".");
}
log2.LogWarning(val);
}
else if (NetworkManager.Singleton.IsServer)
{
Merge(ChoiceTable.OwnedBy(sender, dictionary, OwnerOf));
Broadcast();
}
else if (sender == 0L)
{
_table.Clear();
Merge(dictionary);
}
}
private static ulong? OwnerOf(ulong playerObjectId)
{
if (!NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(playerObjectId, out var value) || !((Object)(object)((Component)value).GetComponent<PlayerData>() != (Object)null))
{
return null;
}
return value.OwnerClientId;
}
private void Merge(IReadOnlyDictionary<ulong, string> entries)
{
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Expected O, but got Unknown
foreach (KeyValuePair<ulong, string> entry in entries)
{
_table[entry.Key] = entry.Value;
}
try
{
this.Changed?.Invoke();
}
catch (Exception ex)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(26, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Applying ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(_kind.ToLowerInvariant());
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" choices failed: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
}
}
private void Broadcast()
{
byte[] bytes = ChoiceTable.Encode(_table);
foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds)
{
if (connectedClientsId != 0L)
{
Send(bytes, connectedClientsId);
}
}
}
private unsafe void Send(byte[] bytes, ulong to)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
FastBufferWriter val = default(FastBufferWriter);
((FastBufferWriter)(ref val))..ctor(bytes.Length + 4, (Allocator)2, -1);
try
{
int num = bytes.Length;
((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
((FastBufferWriter)(ref val)).WriteBytesSafe(bytes, -1, 0);
NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage(_messageName, to, val, (NetworkDelivery)4);
}
finally
{
((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
}
}
}
internal static class ChoiceTable
{
public const int MaxNameLength = 64;
public const int MaxEntries = 64;
public const int MaxBytes = 16384;
public static byte[] Encode(IReadOnlyDictionary<ulong, string> table)
{
using MemoryStream memoryStream = new MemoryStream();
using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8))
{
binaryWriter.Write(table.Count);
foreach (KeyValuePair<ulong, string> item in table)
{
binaryWriter.Write(item.Key);
binaryWriter.Write(item.Value);
}
}
return memoryStream.ToArray();
}
public static Dictionary<ulong, string> Decode(byte[] bytes)
{
if (bytes == null || bytes.Length > 16384)
{
return null;
}
try
{
using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes), Encoding.UTF8);
int num = binaryReader.ReadInt32();
if (num < 0 || num > 64)
{
return null;
}
Dictionary<ulong, string> dictionary = new Dictionary<ulong, string>(num);
for (int i = 0; i < num; i++)
{
ulong key = binaryReader.ReadUInt64();
string text = binaryReader.ReadString();
if (text.Length == 0 || text.Length > 64)
{
return null;
}
dictionary[key] = text;
}
return (binaryReader.BaseStream.Position == binaryReader.BaseStream.Length) ? dictionary : null;
}
catch (Exception ex) when (ex is EndOfStreamException || ex is IOException || ex is FormatException || ex is ArgumentException)
{
return null;
}
}
public static string PickRandom(IReadOnlyList<string> horns, string previous, Random rng)
{
if (horns.Count == 0)
{
return null;
}
if (horns.Count == 1)
{
return horns[0];
}
int num = -1;
for (int i = 0; i < horns.Count; i++)
{
if (horns[i] == previous)
{
num = i;
}
}
if (num < 0)
{
return horns[rng.Next(horns.Count)];
}
int num2 = rng.Next(horns.Count - 1);
return horns[(num2 >= num) ? (num2 + 1) : num2];
}
public static Dictionary<ulong, string> OwnedBy(ulong sender, IReadOnlyDictionary<ulong, string> claimed, Func<ulong, ulong?> ownerOf)
{
Dictionary<ulong, string> dictionary = new Dictionary<ulong, string>();
foreach (KeyValuePair<ulong, string> item in claimed)
{
if (ownerOf(item.Key) == sender)
{
dictionary[item.Key] = item.Value;
}
}
return dictionary;
}
}
internal sealed class CustomTab
{
private class State
{
public CustomTab Tab;
public int TabIndex;
public UnlockableContentGridUi Grid;
public PlayerData Player;
public bool Initialising;
}
public static readonly List<CustomTab> All = new List<CustomTab>();
private readonly string _title;
private readonly string _iconFile;
private readonly Choices _choices;
private readonly Func<IReadOnlyList<ChoiceContent>> _entries;
private readonly Action<PlayerData, string> _select;
private readonly Action<UnlockablePlayerCustomizationSelector, PlayerData, string> _hover;
private readonly Action<UnlockablePlayerCustomizationSelector, PlayerData> _leave;
private Sprite _icon;
private static readonly Dictionary<UnlockablePlayerCustomizationSelector, List<State>> States = new Dictionary<UnlockablePlayerCustomizationSelector, List<State>>();
private static readonly Dictionary<Tab, string> OurTabs = new Dictionary<Tab, string>();
public CustomTab(string title, string iconFile, Choices choices, Func<IReadOnlyList<ChoiceContent>> entries, Action<PlayerData, string> select, Action<UnlockablePlayerCustomizationSelector, PlayerData, string> hover, Action<UnlockablePlayerCustomizationSelector, PlayerData> leave)
{
_title = title;
_iconFile = iconFile;
_choices = choices;
_entries = entries;
_select = select;
_hover = hover;
_leave = leave;
}
private ChoiceContent Find(string name)
{
IReadOnlyList<ChoiceContent> readOnlyList = _entries();
return readOnlyList.FirstOrDefault((ChoiceContent e) => e.ChoiceName == name) ?? readOnlyList[0];
}
private static List<State> StatesOf(UnlockablePlayerCustomizationSelector selector)
{
if (!States.TryGetValue(selector, out var value))
{
return new List<State>();
}
return value;
}
private static State Active(UnlockablePlayerCustomizationSelector selector)
{
return StatesOf(selector).FirstOrDefault((State s) => (Object)(object)s.Grid != (Object)null && selector.tabs.CurrentTabIndex == s.TabIndex);
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "Init")]
[HarmonyPrefix]
private static void BeforeInit(UnlockablePlayerCustomizationSelector __instance, PlayerData playerData)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)playerData == (Object)null || (int)playerData.type != 0)
{
return;
}
if (!States.TryGetValue(__instance, out var value))
{
value = (States[__instance] = new List<State>());
}
foreach (CustomTab tab in All)
{
State state = value.FirstOrDefault((State s) => s.Tab == tab && (Object)(object)s.Grid != (Object)null);
if (state != null)
{
state.Player = playerData;
continue;
}
try
{
value.Add(tab.Build(__instance, playerData));
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Couldn't add the " + tab._title + " tab; saved choices still apply. " + ex));
}
}
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "Init")]
[HarmonyPostfix]
private static void AfterInit(UnlockablePlayerCustomizationSelector __instance, PlayerData playerData)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)playerData == (Object)null || (int)playerData.type != 0)
{
return;
}
foreach (CustomTab item in All)
{
item._choices.Announce(playerData);
}
foreach (State item2 in from s in StatesOf(__instance)
where (Object)(object)s.Grid != (Object)null
select s)
{
item2.Initialising = true;
try
{
item2.Grid.Init(item2.Tab._entries().Cast<UnlockableContent>().ToList(), (UnlockableContent)(object)item2.Tab.Find(item2.Tab._choices.Saved(playerData)), 0);
LabelTiles(item2.Grid);
}
finally
{
item2.Initialising = false;
}
}
}
private State Build(UnlockablePlayerCustomizationSelector selector, PlayerData player)
{
Tab val = selector.tabs.tabs[selector.carAccessoryGridTabIndex];
GameObject val2 = (((Component)val.container).transform.IsChildOf(((Component)val.containerLayout).transform) ? ((Component)val.containerLayout).gameObject : ((Component)val.container).gameObject);
GameObject val3 = Object.Instantiate<GameObject>(val2, val2.transform.parent);
((Object)val3).name = "MoreCustomisations_" + _title + "Tab";
GameObject val4 = Object.Instantiate<GameObject>(val.root, val.root.transform.parent);
((Object)val4).name = "MoreCustomisations_" + _title + "Grid";
try
{
return Wire(selector, player, val, val2, val3, val4);
}
catch
{
Object.Destroy((Object)(object)val3);
Object.Destroy((Object)(object)val4);
throw;
}
}
private State Wire(UnlockablePlayerCustomizationSelector selector, PlayerData player, Tab source, GameObject headerSource, GameObject header, GameObject root)
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Expected O, but got Unknown
//IL_00da: Unknown result type (might be due to invalid IL or missing references)
//IL_00e4: Expected O, but got Unknown
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Expected O, but got Unknown
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
//IL_01d2: Expected O, but got Unknown
UnlockableContentGridUi componentInChildren = root.GetComponentInChildren<UnlockableContentGridUi>(true);
if ((Object)(object)componentInChildren == (Object)null)
{
throw new InvalidOperationException("no grid under the Accessory tab's root");
}
Tab val = new Tab();
FieldInfo[] fields = typeof(Tab).GetFields(BindingFlags.Instance | BindingFlags.Public);
foreach (FieldInfo fieldInfo in fields)
{
fieldInfo.SetValue(val, fieldInfo.GetValue(source));
}
val.container = Twin<GradientImageWithBorderRadius>(source.container, headerSource, header);
val.containerLayout = Twin<LayoutElement>(source.containerLayout, headerSource, header);
val.headerLabel = Twin<TMP_Text>(source.headerLabel, headerSource, header);
val.iconHolder = Twin<Image>(source.iconHolder, headerSource, header);
val.root = root;
val.icon = Icon();
val.onSelect = new UnityEvent();
val.onDeselect = new UnityEvent();
OurTabs[val] = _title;
selector.tabs.tabs = selector.tabs.tabs.Append(val).ToArray();
componentInChildren.type = (UnlockableContentType)3;
componentInChildren.onSelected = new UnityEvent<UnlockableContent, UnlockableContentGridElementUi>();
componentInChildren.onHovered = new UnityEvent<UnlockableContent, UnlockableContentGridElementUi>();
UnlockableContentInfoDisplay info = Nearest<UnlockableContentInfoDisplay>(((Component)componentInChildren).transform);
State state = new State
{
Tab = this,
TabIndex = selector.tabs.tabs.Length - 1,
Grid = componentInChildren,
Player = player
};
componentInChildren.onSelected.AddListener((UnityAction<UnlockableContent, UnlockableContentGridElementUi>)delegate(UnlockableContent content, UnlockableContentGridElementUi _)
{
if (!state.Initialising && content is ChoiceContent choiceContent)
{
_select(state.Player, choiceContent.ChoiceName);
}
});
componentInChildren.onHovered.AddListener((UnityAction<UnlockableContent, UnlockableContentGridElementUi>)delegate(UnlockableContent content, UnlockableContentGridElementUi element)
{
if (selector.tabs.CurrentTabIndex == state.TabIndex && content is ChoiceContent choiceContent)
{
if ((Object)(object)info != (Object)null)
{
info.UpdateInfos(content, element);
}
if (!state.Initialising)
{
_hover(selector, state.Player, choiceContent.ChoiceName);
}
}
});
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(53, 4, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(_title);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" tab added for local player ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(player.LocalIndex + 1);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" (");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(_entries().Count);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" entries, info panel ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(((Object)(object)info != (Object)null) ? "found" : "not found");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(").");
}
log.LogInfo(val2);
return state;
}
private static T Twin<T>(T original, GameObject sourceRoot, GameObject cloneRoot) where T : Component
{
if ((Object)(object)original == (Object)null)
{
return default(T);
}
List<int> list = new List<int>();
Transform val = ((Component)original).transform;
while ((Object)(object)val != (Object)(object)sourceRoot.transform)
{
if ((Object)(object)val == (Object)null)
{
throw new InvalidOperationException(((Object)(object)original).name + " isn't under the tab header " + ((Object)sourceRoot).name);
}
list.Insert(0, val.GetSiblingIndex());
val = val.parent;
}
Transform val2 = cloneRoot.transform;
foreach (int item in list)
{
val2 = val2.GetChild(item);
}
return ((Component)val2).GetComponent<T>();
}
private static void LabelTiles(UnlockableContentGridUi grid)
{
UnlockableContentGridElementUi[] componentsInChildren = ((Component)grid.elementContainer).GetComponentsInChildren<UnlockableContentGridElementUi>(true);
foreach (UnlockableContentGridElementUi val in componentsInChildren)
{
if (Traverse.Create((object)val).Field("_unlockableContent").GetValue() is ChoiceContent choiceContent)
{
val.indexLabel.text = choiceContent.ChoiceName;
val.indexLabel.enableAutoSizing = true;
val.indexLabel.fontSizeMin = 8f;
val.indexLabel.fontSizeMax = Mathf.Max(val.indexLabel.fontSize, 8f);
}
}
}
private Sprite Icon()
{
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_icon != (Object)null)
{
return _icon;
}
Texture2D val = Art.Load(_iconFile);
if ((Object)(object)val == (Object)null)
{
return null;
}
_icon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
((Object)_icon).name = "MoreCustomisations_Tab" + _title;
return _icon;
}
private static T Nearest<T>(Transform from) where T : Component
{
Transform val = from;
while ((Object)(object)val != (Object)null)
{
T componentInChildren = ((Component)val).GetComponentInChildren<T>(true);
if ((Object)(object)componentInChildren != (Object)null)
{
return componentInChildren;
}
val = val.parent;
}
return default(T);
}
private static void Leave(UnlockablePlayerCustomizationSelector selector)
{
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
bool flag = default(bool);
foreach (State item in from s in StatesOf(selector)
where (Object)(object)s.Grid != (Object)null
select s)
{
try
{
item.Tab._leave(selector, item.Player);
}
catch (Exception ex)
{
ManualLogSource log = Plugin.Log;
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(25, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Leaving the ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item.Tab._title);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" tab failed: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
}
}
}
[HarmonyPatch(typeof(Tab), "Init")]
[HarmonyPostfix]
private static void LabelOurTab(Tab __instance)
{
if (OurTabs.TryGetValue(__instance, out var value))
{
__instance.headerLabel.text = value;
}
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "GetGridFromTab")]
[HarmonyPrefix]
private static bool GridFromTab(UnlockablePlayerCustomizationSelector __instance, ref GridUI __result)
{
State state = Active(__instance);
if (state == null)
{
return true;
}
__result = (GridUI)(object)state.Grid;
return false;
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "UpdateGridHoveredView")]
[HarmonyPrefix]
private static bool HoveredView(UnlockablePlayerCustomizationSelector __instance, ref (UnlockableContentType, object content) __result)
{
Leave(__instance);
State state = Active(__instance);
if (state == null)
{
return true;
}
state.Initialising = true;
try
{
state.Grid.HoverSelected();
}
finally
{
state.Initialising = false;
}
__result = ((UnlockableContentType)1, content: state.Grid.HoveredContent);
return false;
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "LockView")]
[HarmonyPostfix]
private static void Lock(UnlockablePlayerCustomizationSelector __instance)
{
foreach (State item in from s in StatesOf(__instance)
where (Object)(object)s.Grid != (Object)null
select s)
{
item.Grid.Lock();
}
Leave(__instance);
}
[HarmonyPatch(typeof(UnlockablePlayerCustomizationSelector), "Unlock")]
[HarmonyPostfix]
private static void Unlock(UnlockablePlayerCustomizationSelector __instance)
{
foreach (State item in from s in StatesOf(__instance)
where (Object)(object)s.Grid != (Object)null
select s)
{
item.Grid.Unlock();
}
}
[HarmonyPatch(typeof(UnlockableContentGridUi), "Init", new Type[] { typeof(UnlockableContentType) })]
[HarmonyPrefix]
private static bool SkipSelfInit(UnlockableContentGridUi __instance)
{
return !States.Values.Any((List<State> states) => states.Any((State s) => (Object)(object)s.Grid == (Object)(object)__instance));
}
[HarmonyPatch(typeof(UnlockableContentManager), "HasUnlockedContent")]
[HarmonyPostfix]
private static void ChoicesAreUnlocked(UnlockableContent content, ref bool __result)
{
if (content is ChoiceContent)
{
__result = true;
}
}
[HarmonyPatch(typeof(UnlockableContentInfoDisplay), "UpdateInfos")]
[HarmonyPostfix]
private static void ShowChoiceName(UnlockableContentInfoDisplay __instance, UnlockableContent content)
{
if (content is ChoiceContent choiceContent)
{
__instance.unlockInfosLabel.text = choiceContent.ChoiceName;
}
}
}
internal static class HornChoices
{
public static readonly Choices Choices = new Choices("Horn", "Horns", "Stock", Resolved);
private static readonly Dictionary<int, string> Rolls = new Dictionary<int, string>();
private static readonly Random Rng = new Random();
private static GameManager _roundsHookedTo;
public static string Get(PlayerData player)
{
return Choices.Get(player);
}
public static void SetLocal(PlayerData player, string horn)
{
if (horn == "Random")
{
Reroll(player);
}
Choices.SetLocal(player, horn);
}
private static string Resolved(PlayerData player, string saved)
{
if (saved != "Random")
{
return saved;
}
int localIndex = player.LocalIndex;
if (!Rolls.ContainsKey(localIndex))
{
Reroll(player);
}
return Rolls[localIndex];
}
private static void Reroll(PlayerData player)
{
int localIndex = player.LocalIndex;
Rolls.TryGetValue(localIndex, out var value);
Rolls[localIndex] = ChoiceTable.PickRandom(Horns.Customs, value, Rng) ?? "Stock";
}
private static void OnStateChange(GameState state)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
if ((int)state != 4)
{
return;
}
foreach (PlayerData item in new List<PlayerData>(Choices.LocalPlayers))
{
if (!((Object)(object)item == (Object)null) && !(Choices.Saved(item) != "Random"))
{
Reroll(item);
Choices.Announce(item);
}
}
}
public static void Tick()
{
GameManager instance = GameManager.instance;
if ((Object)(object)instance != (Object)null && (Object)(object)instance != (Object)(object)_roundsHookedTo)
{
_roundsHookedTo = instance;
instance.onStateChange.AddListener((UnityAction<GameState>)OnStateChange);
}
Choices.Tick();
}
}
internal static class HornPlayback
{
[HarmonyPatch(typeof(CarEffects), "Honk")]
[HarmonyPostfix]
[HarmonyPriority(600)]
private static void OnHonk(CarEffects __instance)
{
AudioClip val = Horns.Clip(HornChoices.Get(((Object)(object)__instance.car != (Object)null) ? __instance.car.player : null));
if (!((Object)(object)val == (Object)null))
{
SplitScreenAudioSource honkAudioSource = __instance.honkAudioSource;
honkAudioSource.SwitchClip(val);
honkAudioSource.SetVolume(Plugin.HornVolume.Value);
honkAudioSource.SetPitch(1f);
__instance.splitScreenAudioPlayers.Play(honkAudioSource);
}
}
}
internal static class Horns
{
public const string Stock = "Stock";
public const string Random = "Random";
private static List<ChoiceContent> _all;
private static List<string> _customs;
private static Texture2D _defaultIcon;
private static Texture2D _randomIcon;
public static IReadOnlyList<ChoiceContent> All => _all ?? (_all = (from n in new string[2] { "Stock", "Random" }.Concat(Customs)
select ChoiceContent.Create("Horn", n, Icon(n))).ToList());
public static IReadOnlyList<string> Customs => _customs ?? (_customs = (from c in Sounds.Clips
select ((Object)c).name into n
where n != "Stock" && n != "Random"
select n).ToList());
private static Texture2D Icon(string name)
{
object obj = Sounds.Texture(name);
if (obj == null)
{
if (!(name == "Random"))
{
obj = _defaultIcon;
if (obj == null)
{
return _defaultIcon = Art.Load("Horn.png");
}
}
else
{
obj = _randomIcon ?? (_randomIcon = Art.Load("Random.png"));
}
}
return (Texture2D)obj;
}
public static AudioClip Clip(string name)
{
if (!(name == "Stock") && !(name == "Random"))
{
return ((IEnumerable<AudioClip>)Sounds.Clips).FirstOrDefault((Func<AudioClip, bool>)((AudioClip c) => ((Object)c).name == name));
}
return null;
}
}
internal static class HornTab
{
private static AudioSource _preview;
private static readonly Random PreviewRng = new Random();
public static CustomTab Create()
{
return new CustomTab("Horn", "TabHorn.png", HornChoices.Choices, () => Horns.All, HornChoices.SetLocal, delegate(UnlockablePlayerCustomizationSelector selector, PlayerData player, string horn)
{
Preview(horn);
}, delegate
{
StopPreview();
});
}
private static void Preview(string hornName)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Expected O, but got Unknown
if (hornName == "Random")
{
hornName = ChoiceTable.PickRandom(Horns.Customs, null, PreviewRng);
}
AudioClip val = Horns.Clip(hornName);
if (!((Object)(object)val == (Object)null))
{
if ((Object)(object)_preview == (Object)null)
{
GameObject val2 = new GameObject("MoreCustomisations_HornPreview");
Object.DontDestroyOnLoad((Object)val2);
_preview = val2.AddComponent<AudioSource>();
_preview.spatialBlend = 0f;
}
_preview.Stop();
_preview.PlayOneShot(val, Plugin.HornVolume.Value);
}
}
private static void StopPreview()
{
if ((Object)(object)_preview != (Object)null)
{
_preview.Stop();
}
}
}
[BepInPlugin("onlynothing.morecustomisations", "MoreCustomisations", "0.5.0")]
public class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Log;
internal static ConfigFile Cfg;
internal static ConfigEntry<float> HornVolume;
private static Harmony _harmony;
private void Awake()
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Cfg = ((BaseUnityPlugin)this).Config;
HornVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Horns", "Volume", 1f, "Volume of custom horn sounds, 0 to 1.");
_harmony = new Harmony("onlynothing.morecustomisations");
Sounds.Load();
_ = Cars.Loaded;
_harmony.PatchAll(typeof(HornPlayback));
CustomTab.All.Add(HornTab.Create());
CustomTab.All.Add(CarPatches.CreateTab());
_harmony.PatchAll(typeof(CustomTab));
_harmony.PatchAll(typeof(CarPatches));
Log.LogInfo((object)"MoreCustomisations loaded.");
}
private void Update()
{
HornChoices.Tick();
Cars.Choices.Tick();
}
}
internal static class Sounds
{
private const string BundleFile = "morecustomisations.bundle";
private static Texture2D[] _textures = (Texture2D[])(object)new Texture2D[0];
public static AudioClip[] Clips { get; private set; } = (AudioClip[])(object)new AudioClip[0];
public static void Load()
{
//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
//IL_00c9: Expected O, but got Unknown
string text = Path.Combine(Path.GetDirectoryName(typeof(Sounds).Assembly.Location), "morecustomisations.bundle");
if (!File.Exists(text))
{
Plugin.Log.LogInfo((object)"No sound bundle next to the DLL; only the stock horn is available.");
return;
}
AssetBundle assetBundle = AssetBundle.LoadFromFileAsync(text).assetBundle;
if ((Object)(object)assetBundle == (Object)null)
{
Plugin.Log.LogWarning((object)"Sound bundle failed to load; was it built with Unity 2022.3 for Windows?");
return;
}
Clips = (from c in assetBundle.LoadAllAssetsAsync<AudioClip>().allAssets.OfType<AudioClip>()
orderby ((Object)c).name
select c).ToArray();
_textures = assetBundle.LoadAllAssetsAsync<Texture2D>().allAssets.OfType<Texture2D>().ToArray();
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(25, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Horns loaded: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(string.Join(", ", Clips.Select((AudioClip c) => ((Object)c).name)));
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(_textures.Length);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" icon(s))");
}
log.LogInfo(val);
}
public static Texture2D Texture(string name)
{
return ((IEnumerable<Texture2D>)_textures).FirstOrDefault((Func<Texture2D, bool>)((Texture2D t) => ((Object)t).name == name));
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "onlynothing.morecustomisations";
public const string PLUGIN_NAME = "MoreCustomisations";
public const string PLUGIN_VERSION = "0.5.0";
}
}