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 originalTrafficCaps = new Dictionary(); private readonly Dictionary originalVehicleSpeeds = new Dictionary(); private readonly Dictionary originalVehicleDefaultSpeeds = new Dictionary(); private readonly Dictionary originalRoadRage = new Dictionary(); private readonly Dictionary forcedMovements = new Dictionary(); private readonly List groundVehiclePrefabs = new List(); private readonly Queue uncheckedVehiclePrefabs = new Queue(); private readonly HashSet discoveredVehiclePrefabGuids = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet acceptedGroundVehicleGuids = new HashSet(StringComparer.OrdinalIgnoreCase); private Delegate forceInputDelegate; private ConfigEntry normalSpawnMultiplier; private ConfigEntry doomsdayVehicleSpeedMultiplier; private ConfigEntry doomsdaySpawnMultiplier; private ConfigEntry doomsdayDurationSeconds; private ConfigEntry showDoomsdayBanner; private ConfigEntry 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("Traffic", "SpawnMultiplier", 3f, "Traffic spawn frequency and capacity multiplier outside Doomsday."); doomsdayVehicleSpeedMultiplier = ((BaseUnityPlugin)this).Config.Bind("Doomsday", "VehicleSpeedMultiplier", 50f, "AI vehicle top-speed multiplier during Doomsday."); doomsdaySpawnMultiplier = ((BaseUnityPlugin)this).Config.Bind("Doomsday", "SpawnMultiplier", 100f, "Traffic spawn frequency and capacity multiplier during Doomsday."); doomsdayDelaySeconds = ((BaseUnityPlugin)this).Config.Bind("Doomsday", "DelaySeconds", 60f, "Fixed normal-mode delay before the next Doomsday."); doomsdayDurationSeconds = ((BaseUnityPlugin)this).Config.Bind("Doomsday", "DurationSeconds", 60f, "How long one Doomsday lasts."); showDoomsdayBanner = ((BaseUnityPlugin)this).Config.Bind("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(); foreach (PlayerVehicleRoadAI val in array) { if (!Object.op_Implicit((Object)(object)val)) { continue; } PlayerVehicleRoadMovement val2 = ((Component)val).GetComponent(); if (!Object.op_Implicit((Object)(object)val2)) { val2 = ((Component)val).GetComponentInChildren(); } 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(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(); 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(); 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]; } }