using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HawkNetworking;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Wobbly Life Traffic Plus")]
[assembly: AssemblyDescription("Ground-compatible varied traffic and Doomsday events for Wobbly Life.")]
[assembly: AssemblyCompany("Zappyix")]
[assembly: AssemblyProduct("Wobbly Life Traffic Plus")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyVersion("1.0.6.0")]
namespace Zappyix.WobblyLife.TrafficPlus;
[BepInPlugin("zappyix.wobblylife.trafficplus", "Wobbly Life Traffic Plus", "1.0.6")]
public sealed class WobblyLifeTrafficPlusPlugin : BaseUnityPlugin
{
public const string PluginGuid = "zappyix.wobblylife.trafficplus";
public const string PluginName = "Wobbly Life Traffic Plus";
public const string PluginVersion = "1.0.6";
private const BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Type TrafficManagerType = typeof(PlayerNPCNavigation).Assembly.GetType("TrafficManager");
private static readonly FieldInfo TrafficLastUpdate = ((TrafficManagerType == null) ? null : TrafficManagerType.GetField("trafficUpdateTime", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly FieldInfo TrafficMaxVehicles = ((TrafficManagerType == null) ? null : TrafficManagerType.GetField("maxVehicles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly FieldInfo VehicleDefaultTopSpeed = typeof(PlayerVehicleRoadMovement).GetField("defaultTopSpeedMph", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly FieldInfo VehicleTopSpeed = typeof(PlayerVehicleRoadMovement).GetField("topSpeedMph", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly FieldInfo VehiclePreSimulate = typeof(PlayerVehicleRoadMovement).GetField("onPreSimulate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly FieldInfo VehicleRoadRage = typeof(PlayerVehicleRoadAI).GetField("bRoadRage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly MethodInfo SpawnAi = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("SpawnAI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly MethodInfo GetVehicleSpawn = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("GetVehicleSpawn", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly MethodInfo SpawnAiVehicle = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("SpawnAIVehicle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly MethodInfo GetAvailableVehicleAi = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("GetAvaliableVehicleAI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly MethodInfo MakeUnavailable = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("MakeUnavaliable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly MethodInfo SetupAiVehicle = ((TrafficManagerType == null) ? null : TrafficManagerType.GetMethod("SetupAIVehicle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
private static readonly FieldInfo AllVehicleDatabases = typeof(VehicleManager).GetField("allVehiclesAssetReferences", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly MethodInfo ForceInputMethod = typeof(WobblyLifeTrafficPlusPlugin).GetMethod("ForceDoomsdayInput", BindingFlags.Static | BindingFlags.NonPublic);
private readonly Dictionary<int, uint> originalTrafficCaps = new Dictionary<int, uint>();
private readonly Dictionary<int, float> originalVehicleSpeeds = new Dictionary<int, float>();
private readonly Dictionary<int, float> originalVehicleDefaultSpeeds = new Dictionary<int, float>();
private readonly Dictionary<int, bool> originalRoadRage = new Dictionary<int, bool>();
private readonly Dictionary<int, PlayerVehicleRoadMovement> forcedMovements = new Dictionary<int, PlayerVehicleRoadMovement>();
private readonly List<object> groundVehiclePrefabs = new List<object>();
private readonly Queue<object> uncheckedVehiclePrefabs = new Queue<object>();
private readonly HashSet<string> discoveredVehiclePrefabGuids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> acceptedGroundVehicleGuids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private Delegate forceInputDelegate;
private ConfigEntry<float> normalSpawnMultiplier;
private ConfigEntry<float> doomsdayVehicleSpeedMultiplier;
private ConfigEntry<float> doomsdaySpawnMultiplier;
private ConfigEntry<float> doomsdayDurationSeconds;
private ConfigEntry<bool> showDoomsdayBanner;
private ConfigEntry<float> doomsdayDelaySeconds;
private float nextScanTime;
private float nextTrafficSpawnTime;
private float nextDoomsdayTime;
private float doomsdayEndTime;
private float bannerEndTime;
private float nextVehicleCatalogRefreshTime;
private bool doomsdayActive;
private bool warnedNotHost;
private GUIStyle bannerStyle;
private void Awake()
{
normalSpawnMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Traffic", "SpawnMultiplier", 3f, "Traffic spawn frequency and capacity multiplier outside Doomsday.");
doomsdayVehicleSpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Doomsday", "VehicleSpeedMultiplier", 50f, "AI vehicle top-speed multiplier during Doomsday.");
doomsdaySpawnMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Doomsday", "SpawnMultiplier", 100f, "Traffic spawn frequency and capacity multiplier during Doomsday.");
doomsdayDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Doomsday", "DelaySeconds", 60f, "Fixed normal-mode delay before the next Doomsday.");
doomsdayDurationSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Doomsday", "DurationSeconds", 60f, "How long one Doomsday lasts.");
showDoomsdayBanner = ((BaseUnityPlugin)this).Config.Bind<bool>("Doomsday", "ShowBanner", true, "Show the red Doomsday countdown at the top of the screen.");
if (Mathf.Approximately(doomsdayVehicleSpeedMultiplier.Value, 20f))
{
doomsdayVehicleSpeedMultiplier.Value = 50f;
}
if (Mathf.Approximately(doomsdaySpawnMultiplier.Value, 18f))
{
doomsdaySpawnMultiplier.Value = 100f;
}
ScheduleNextDoomsday();
((BaseUnityPlugin)this).Logger.LogInfo((object)"Wobbly Life Traffic Plus 1.0.6 loaded.");
}
private void Update()
{
bool flag = IsHost();
if (Time.unscaledTime >= nextScanTime)
{
nextScanTime = Time.unscaledTime + 0.5f;
ApplyTraffic(flag);
ApplyVehicleSpeed(flag);
}
if (!flag)
{
if (!warnedNotHost)
{
warnedNotHost = true;
((BaseUnityPlugin)this).Logger.LogInfo((object)"Traffic and Doomsday are host-controlled.");
}
return;
}
warnedNotHost = false;
if (!doomsdayActive && Time.unscaledTime >= nextDoomsdayTime)
{
BeginDoomsday();
}
else if (doomsdayActive && Time.unscaledTime >= doomsdayEndTime)
{
EndDoomsday();
}
RunTrafficSpawner();
}
private static bool IsHost()
{
try
{
HawkNetworkManager defaultInstance = HawkNetworkManager.DefaultInstance;
return (Object)(object)defaultInstance != (Object)null && defaultInstance.IsServer();
}
catch
{
return false;
}
}
private void ApplyTraffic(bool isHost)
{
if (!isHost || TrafficMaxVehicles == null)
{
return;
}
Object[] array = FindTrafficManagers();
float num = CurrentSpawnMultiplier();
foreach (Object val in array)
{
if (!Object.op_Implicit(val))
{
continue;
}
int instanceID = val.GetInstanceID();
if (!originalTrafficCaps.TryGetValue(instanceID, out var value))
{
value = (uint)TrafficMaxVehicles.GetValue(val);
if (value == 0)
{
value = 5u;
}
originalTrafficCaps[instanceID] = value;
}
uint num2 = (uint)Mathf.Clamp(Mathf.RoundToInt((float)value * num), 1, 120);
TrafficMaxVehicles.SetValue(val, num2);
}
}
private void ApplyVehicleSpeed(bool isHost)
{
if (!isHost || VehicleTopSpeed == null || VehicleDefaultTopSpeed == null)
{
return;
}
PlayerVehicleRoadAI[] array = Object.FindObjectsOfType<PlayerVehicleRoadAI>();
foreach (PlayerVehicleRoadAI val in array)
{
if (!Object.op_Implicit((Object)(object)val))
{
continue;
}
PlayerVehicleRoadMovement val2 = ((Component)val).GetComponent<PlayerVehicleRoadMovement>();
if (!Object.op_Implicit((Object)(object)val2))
{
val2 = ((Component)val).GetComponentInChildren<PlayerVehicleRoadMovement>();
}
if (!Object.op_Implicit((Object)(object)val2))
{
continue;
}
int instanceID = ((Object)val2).GetInstanceID();
if (!originalVehicleSpeeds.TryGetValue(instanceID, out var value))
{
value = (float)VehicleDefaultTopSpeed.GetValue(val2);
if (value <= 0f)
{
value = (float)VehicleTopSpeed.GetValue(val2);
}
if (value <= 0f)
{
value = 100f;
}
originalVehicleSpeeds[instanceID] = value;
originalVehicleDefaultSpeeds[instanceID] = (float)VehicleDefaultTopSpeed.GetValue(val2);
}
float num = (doomsdayActive ? Mathf.Max(1f, doomsdayVehicleSpeedMultiplier.Value) : 1f);
VehicleDefaultTopSpeed.SetValue(val2, originalVehicleDefaultSpeeds[instanceID] * num);
VehicleTopSpeed.SetValue(val2, value * num);
int instanceID2 = ((Object)val).GetInstanceID();
if (VehicleRoadRage != null && !originalRoadRage.ContainsKey(instanceID2))
{
originalRoadRage[instanceID2] = (bool)VehicleRoadRage.GetValue(val);
}
if (doomsdayActive && ((Behaviour)val).enabled)
{
if (VehicleRoadRage != null)
{
VehicleRoadRage.SetValue(val, true);
}
AttachForcedInput(val2);
}
else
{
if (VehicleRoadRage != null && originalRoadRage.TryGetValue(instanceID2, out var value2))
{
VehicleRoadRage.SetValue(val, value2);
}
DetachForcedInput(val2);
}
}
}
private void AttachForcedInput(PlayerVehicleRoadMovement movement)
{
if (!Object.op_Implicit((Object)(object)movement) || VehiclePreSimulate == null || ForceInputMethod == null)
{
return;
}
int instanceID = ((Object)movement).GetInstanceID();
if (!forcedMovements.ContainsKey(instanceID))
{
if ((object)forceInputDelegate == null)
{
forceInputDelegate = Delegate.CreateDelegate(VehiclePreSimulate.FieldType, ForceInputMethod);
}
Delegate a = (Delegate)VehiclePreSimulate.GetValue(movement);
VehiclePreSimulate.SetValue(movement, Delegate.Combine(a, forceInputDelegate));
forcedMovements[instanceID] = movement;
}
}
private void DetachForcedInput(PlayerVehicleRoadMovement movement)
{
if (Object.op_Implicit((Object)(object)movement) && !(VehiclePreSimulate == null) && (object)forceInputDelegate != null)
{
int instanceID = ((Object)movement).GetInstanceID();
if (forcedMovements.ContainsKey(instanceID))
{
Delegate source = (Delegate)VehiclePreSimulate.GetValue(movement);
VehiclePreSimulate.SetValue(movement, Delegate.Remove(source, forceInputDelegate));
forcedMovements.Remove(instanceID);
}
}
}
private static void ForceDoomsdayInput(ref VehicleRoadInput input)
{
input.acceleration = ((input.acceleration < -0.05f) ? (-1f) : 1f);
input.bHandbrake = false;
input.bHorn = Time.unscaledTime % 4f < 3f;
}
private void RunTrafficSpawner()
{
if (TrafficLastUpdate == null || SpawnAi == null)
{
return;
}
float num = CurrentSpawnMultiplier();
float num2 = 1f / Mathf.Max(1f, num);
if (Time.unscaledTime < nextTrafficSpawnTime)
{
return;
}
nextTrafficSpawnTime = Time.unscaledTime + num2;
Object[] array = FindTrafficManagers();
foreach (Object val in array)
{
if (!Object.op_Implicit(val))
{
continue;
}
TrafficLastUpdate.SetValue(val, Time.time);
try
{
if (!TrySpawnRandomVehicle(val))
{
SpawnAi.Invoke(val, null);
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("Traffic spawn failed: " + ex.GetBaseException().Message));
}
}
}
private bool TrySpawnRandomVehicle(Object trafficManager)
{
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Expected O, but got Unknown
if (GetVehicleSpawn == null || SpawnAiVehicle == null)
{
return false;
}
RefreshVehicleCatalog();
if (groundVehiclePrefabs.Count == 0)
{
return false;
}
object obj = GetVehicleSpawn.Invoke(trafficManager, null);
if (!(obj is Object) || !Object.op_Implicit((Object)obj))
{
return true;
}
object obj2 = groundVehiclePrefabs[Random.Range(0, groundVehiclePrefabs.Count)];
if (Guid.TryParse(GetAssetGuid(obj2), out var result) && GetAvailableVehicleAi != null && MakeUnavailable != null && SetupAiVehicle != null)
{
object obj3 = GetAvailableVehicleAi.Invoke(trafficManager, new object[1] { result });
if (Object.op_Implicit((Object)((obj3 is Object) ? obj3 : null)))
{
MakeUnavailable.Invoke(trafficManager, new object[1] { obj3 });
SetupAiVehicle.Invoke(trafficManager, new object[2] { obj3, obj });
return true;
}
}
SpawnAiVehicle.Invoke(trafficManager, new object[2] { obj2, obj });
return true;
}
private void RefreshVehicleCatalog()
{
if (Time.unscaledTime < nextVehicleCatalogRefreshTime)
{
return;
}
nextVehicleCatalogRefreshTime = Time.unscaledTime + 0.5f;
Object[] array = Object.FindObjectsOfType(typeof(VehicleManager));
if (array.Length == 0 || AllVehicleDatabases == null || !(AllVehicleDatabases.GetValue(array[0]) is IEnumerable enumerable))
{
return;
}
foreach (object item in enumerable)
{
if (item == null)
{
continue;
}
MethodInfo method = item.GetType().GetMethod("GetAllVehicleReferences", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
IEnumerable enumerable2 = ((method == null) ? null : (method.Invoke(item, null) as IEnumerable));
if (enumerable2 == null)
{
continue;
}
foreach (object item2 in enumerable2)
{
if (item2 != null)
{
FieldInfo field = item2.GetType().GetField("prefab", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object obj = ((field == null) ? null : field.GetValue(item2));
string assetGuid = GetAssetGuid(obj);
if (obj != null && !string.IsNullOrEmpty(assetGuid) && discoveredVehiclePrefabGuids.Add(assetGuid))
{
uncheckedVehiclePrefabs.Enqueue(obj);
}
}
}
}
int num = 4;
while (num-- > 0 && uncheckedVehiclePrefabs.Count > 0)
{
object obj2 = uncheckedVehiclePrefabs.Dequeue();
GameObject val = LoadVehiclePrefab(obj2);
if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponentInChildren<PlayerVehicleRoadMovement>(true)))
{
string assetGuid2 = GetAssetGuid(obj2);
if (!string.IsNullOrEmpty(assetGuid2) && acceptedGroundVehicleGuids.Add(assetGuid2))
{
groundVehiclePrefabs.Add(obj2);
}
}
}
if (groundVehiclePrefabs.Count > 0)
{
((BaseUnityPlugin)this).Logger.LogDebug((object)("Ground-compatible traffic catalogue: " + groundVehiclePrefabs.Count + " vehicles."));
}
}
private GameObject LoadVehiclePrefab(object assetReference)
{
if (assetReference == null)
{
return null;
}
try
{
PropertyInfo property = assetReference.GetType().GetProperty("Asset", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
GameObject val = (GameObject)((property == null) ? null : /*isinst with value type is only supported in some contexts*/);
if (Object.op_Implicit((Object)(object)val))
{
return val;
}
MethodInfo methodInfo = null;
MethodInfo[] methods = assetReference.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (int i = 0; i < methods.Length; i++)
{
if (methods[i].Name == "LoadAssetAsync" && methods[i].IsGenericMethodDefinition && methods[i].GetParameters().Length == 0)
{
methodInfo = methods[i];
break;
}
}
if (methodInfo == null)
{
return null;
}
object obj = methodInfo.MakeGenericMethod(typeof(GameObject)).Invoke(assetReference, null);
if (obj == null)
{
return null;
}
MethodInfo method = obj.GetType().GetMethod("WaitForCompletion", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
return (GameObject)((method == null) ? null : /*isinst with value type is only supported in some contexts*/);
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogDebug((object)("Skipped unreadable vehicle prefab: " + ex.GetBaseException().Message));
return null;
}
}
private static string GetAssetGuid(object prefab)
{
if (prefab == null)
{
return null;
}
PropertyInfo property = prefab.GetType().GetProperty("AssetGUID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (!(property == null))
{
return property.GetValue(prefab, null) as string;
}
return null;
}
private float CurrentSpawnMultiplier()
{
if (!doomsdayActive)
{
return Mathf.Max(1f, normalSpawnMultiplier.Value);
}
return Mathf.Max(1f, doomsdaySpawnMultiplier.Value);
}
private void BeginDoomsday()
{
doomsdayActive = true;
doomsdayEndTime = Time.unscaledTime + Mathf.Max(5f, doomsdayDurationSeconds.Value);
bannerEndTime = Time.unscaledTime + 5f;
nextTrafficSpawnTime = 0f;
((BaseUnityPlugin)this).Logger.LogWarning((object)("DOOMSDAY STARTED: traffic x" + CurrentSpawnMultiplier().ToString("0.#") + ", vehicle speed x" + doomsdayVehicleSpeedMultiplier.Value.ToString("0.#")));
}
private void EndDoomsday()
{
doomsdayActive = false;
nextTrafficSpawnTime = 0f;
ScheduleNextDoomsday();
ApplyTraffic(isHost: true);
ApplyVehicleSpeed(isHost: true);
((BaseUnityPlugin)this).Logger.LogInfo((object)"Doomsday ended. Normal traffic restored.");
}
private void ScheduleNextDoomsday()
{
nextDoomsdayTime = Time.unscaledTime + Mathf.Max(1f, (doomsdayDelaySeconds == null) ? 60f : doomsdayDelaySeconds.Value);
}
private void OnGUI()
{
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Expected O, but got Unknown
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
if (showDoomsdayBanner.Value && doomsdayActive)
{
if (bannerStyle == null)
{
bannerStyle = new GUIStyle(GUI.skin.label);
bannerStyle.alignment = (TextAnchor)4;
bannerStyle.fontSize = 28;
bannerStyle.fontStyle = (FontStyle)1;
bannerStyle.normal.textColor = Color.red;
}
float num = Mathf.Max(0f, doomsdayEndTime - Time.unscaledTime);
string text = ((Time.unscaledTime < bannerEndTime) ? "DOOMSDAY! " : "");
GUI.Label(new Rect(0f, 18f, (float)Screen.width, 42f), text + Mathf.CeilToInt(num) + "s", bannerStyle);
}
}
private void OnDestroy()
{
RestoreAllValues();
}
private void RestoreAllValues()
{
Object[] array = FindTrafficManagers();
for (int i = 0; i < array.Length; i++)
{
if (Object.op_Implicit(array[i]) && originalTrafficCaps.TryGetValue(array[i].GetInstanceID(), out var value) && TrafficMaxVehicles != null)
{
TrafficMaxVehicles.SetValue(array[i], value);
}
}
PlayerVehicleRoadMovement[] array2 = Object.FindObjectsOfType<PlayerVehicleRoadMovement>();
for (int j = 0; j < array2.Length; j++)
{
if (Object.op_Implicit((Object)(object)array2[j]) && originalVehicleSpeeds.TryGetValue(((Object)array2[j]).GetInstanceID(), out var value2) && VehicleTopSpeed != null)
{
VehicleTopSpeed.SetValue(array2[j], value2);
}
if (Object.op_Implicit((Object)(object)array2[j]) && originalVehicleDefaultSpeeds.TryGetValue(((Object)array2[j]).GetInstanceID(), out var value3) && VehicleDefaultTopSpeed != null)
{
VehicleDefaultTopSpeed.SetValue(array2[j], value3);
}
DetachForcedInput(array2[j]);
}
PlayerVehicleRoadAI[] array3 = Object.FindObjectsOfType<PlayerVehicleRoadAI>();
for (int k = 0; k < array3.Length; k++)
{
if (Object.op_Implicit((Object)(object)array3[k]) && VehicleRoadRage != null && originalRoadRage.TryGetValue(((Object)array3[k]).GetInstanceID(), out var value4))
{
VehicleRoadRage.SetValue(array3[k], value4);
}
}
}
private static Object[] FindTrafficManagers()
{
if (!(TrafficManagerType == null))
{
return Object.FindObjectsOfType(TrafficManagerType);
}
return (Object[])(object)new Object[0];
}
}