Decompiled source of ShieldBubble v1.1.0

plugins/ShieldBubble.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

[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("ShieldBubble")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+2ab0bf440f910a5473a0f10a312d094411679934")]
[assembly: AssemblyProduct("ShieldBubble")]
[assembly: AssemblyTitle("ShieldBubble")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ShieldBubble;

public static class ConfigStore
{
	public static float Radius { get; set; } = 30f;

	public static bool Enabled { get; set; } = true;
}
public static class Net
{
	public const float AckTimeoutSeconds = 10f;

	public const float RepushSeconds = 10f;

	public const string RpcModAck = "SRModAck";

	public const string RpcConfig = "SRConfig";

	private static readonly Dictionary<long, float> s_firstSeen = new Dictionary<long, float>();

	private static readonly Dictionary<long, bool> s_acked = new Dictionary<long, bool>();

	private static readonly Dictionary<long, float> s_lastPush = new Dictionary<long, float>();

	private static bool s_dirty;

	private static float s_now;

	private static int s_ackRetransmits;

	public static bool HasServerConfig { get; private set; }

	public static float ServerRadius { get; private set; }

	public static bool ServerEnabled { get; private set; }

	public static (float radius, bool enabled) Effective()
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null || instance.IsServer())
		{
			return (radius: ConfigStore.Radius, enabled: ConfigStore.Enabled);
		}
		if (HasServerConfig)
		{
			return (radius: ServerRadius, enabled: ServerEnabled);
		}
		return (radius: ConfigStore.Radius, enabled: ConfigStore.Enabled);
	}

	public static void ClientSendAck(long serverUid)
	{
		if (!HasServerConfig && serverUid > 0)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null)
			{
				s_ackRetransmits++;
				Plugin.Warn($"[ShieldBubble] ack retransmit n={s_ackRetransmits} → uid {serverUid}");
				instance.InvokeRoutedRPC(serverUid, "SRModAck", Array.Empty<object>());
			}
		}
	}

	public static void ClientOnServerConfig(long sender, float radius, bool enabled)
	{
		ServerRadius = radius;
		ServerEnabled = enabled;
		HasServerConfig = true;
		s_ackRetransmits = 0;
		Plugin.Warn($"[ShieldBubble] config from server: radius={radius:0} m, enabled={enabled}");
	}

	public static void ResetClient()
	{
		HasServerConfig = false;
		ServerRadius = 0f;
		ServerEnabled = false;
		s_ackRetransmits = 0;
	}

	public static void MarkDirty()
	{
		s_dirty = true;
	}

	public static void ServerOnModAck(long senderUid)
	{
		s_acked[senderUid] = true;
		if (ConfigStore.Enabled)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null)
			{
				instance.InvokeRoutedRPC(senderUid, "SRConfig", new object[2]
				{
					ConfigStore.Radius,
					ConfigStore.Enabled
				});
				s_lastPush[senderUid] = s_now;
				Plugin.Warn($"[ShieldBubble] mod ack from '{PeerName(senderUid)}' (uid={senderUid}) → pushed config (radius={ConfigStore.Radius:0} m, enabled={ConfigStore.Enabled})");
			}
		}
	}

	public static void ServerPollOnce(float now)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null || !instance.IsServer())
		{
			return;
		}
		s_now = now;
		List<ZNetPeer> connectedPeers = instance.GetConnectedPeers();
		if (connectedPeers == null)
		{
			return;
		}
		List<long> list = new List<long>();
		foreach (long key in s_firstSeen.Keys)
		{
			if (!PeersContain(connectedPeers, key))
			{
				list.Add(key);
			}
		}
		foreach (long item in list)
		{
			s_firstSeen.Remove(item);
			s_lastPush.Remove(item);
		}
		List<long> list2 = new List<long>();
		foreach (long key2 in s_acked.Keys)
		{
			if (!PeersContain(connectedPeers, key2))
			{
				list2.Add(key2);
			}
		}
		foreach (long item2 in list2)
		{
			s_acked.Remove(item2);
			s_lastPush.Remove(item2);
		}
		if (connectedPeers.Count == 0)
		{
			if (ZRoutedRpc.instance != null)
			{
				s_dirty = false;
			}
			return;
		}
		bool enabled = ConfigStore.Enabled;
		float radius = ConfigStore.Radius;
		ZRoutedRpc instance2 = ZRoutedRpc.instance;
		for (int i = 0; i < connectedPeers.Count; i++)
		{
			ZNetPeer val = connectedPeers[i];
			if (val == null || !val.IsReady())
			{
				continue;
			}
			long uid = val.m_uid;
			if (!enabled)
			{
				continue;
			}
			if (!s_acked.TryGetValue(uid, out var _))
			{
				if (!s_firstSeen.ContainsKey(uid))
				{
					s_firstSeen[uid] = now;
					Plugin.Warn($"[ShieldBubble] peer '{val.m_playerName}' (uid={uid}) waiting for mod ack");
				}
				if (now - s_firstSeen[uid] > 10f)
				{
					Plugin.Warn($"[ShieldBubble] kicking '{val.m_playerName}' (uid={uid}): missing mod (no ack after {10f:0} s)");
					s_firstSeen.Remove(uid);
					s_lastPush.Remove(uid);
					instance.Disconnect(val);
				}
				continue;
			}
			float value2;
			bool flag = s_lastPush.TryGetValue(uid, out value2) && now - value2 >= 10f;
			if ((s_dirty || flag) && instance2 != null)
			{
				instance2.InvokeRoutedRPC(uid, "SRConfig", new object[2] { radius, enabled });
				s_lastPush[uid] = now;
				Plugin.Warn($"[ShieldBubble] pushed config to '{val.m_playerName}' (uid={uid}): radius={radius:0} m, enabled={enabled}");
			}
		}
		if (instance2 != null)
		{
			s_dirty = false;
		}
	}

	private static bool PeersContain(List<ZNetPeer> peers, long uid)
	{
		for (int i = 0; i < peers.Count; i++)
		{
			if (peers[i] != null && peers[i].m_uid == uid)
			{
				return true;
			}
		}
		return false;
	}

	private static string PeerName(long uid)
	{
		ZNet instance = ZNet.instance;
		List<ZNetPeer> list = (((Object)(object)instance != (Object)null) ? instance.GetConnectedPeers() : null);
		if (list != null)
		{
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i] != null && list[i].m_uid == uid)
				{
					return list[i].m_playerName;
				}
			}
		}
		return "?";
	}

	public static void Reset()
	{
		HasServerConfig = false;
		ServerRadius = 0f;
		ServerEnabled = false;
		s_firstSeen.Clear();
		s_acked.Clear();
		s_lastPush.Clear();
		s_dirty = false;
		s_now = 0f;
		s_ackRetransmits = 0;
	}
}
[HarmonyPatch]
internal static class Patches
{
	private static readonly FieldInfo s_radiusTargetField = AccessTools.Field(typeof(ShieldGenerator), "m_radiusTarget");

	private static bool s_warned;

	private static void WarnRadiusFieldMissing()
	{
		if (!s_warned)
		{
			s_warned = true;
			Plugin.Warn("[ShieldBubble] ShieldGenerator.m_radiusTarget field not found; UpdateShield postfix is inactive (API change?)");
		}
	}

	[HarmonyPatch(typeof(ShieldGenerator), "Start")]
	[HarmonyPrefix]
	private static void StartPrefix(ShieldGenerator __instance)
	{
		(float, bool) tuple = Net.Effective();
		if (tuple.Item2)
		{
			var (num, _) = tuple;
			if (!(num <= 0f))
			{
				__instance.m_minShieldRadius = num;
				__instance.m_maxShieldRadius = num;
				__instance.m_startStopSpeed = 5f;
			}
		}
	}

	[HarmonyPatch(typeof(ShieldGenerator), "UpdateShield")]
	[HarmonyPostfix]
	private static void UpdateShieldPostfix(ShieldGenerator __instance, ZNetView ___m_nview, float ___m_lastFuel)
	{
		(float, bool) tuple = Net.Effective();
		if (!tuple.Item2)
		{
			return;
		}
		var (num, _) = tuple;
		if (num <= 0f || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid() || ___m_lastFuel <= 0f)
		{
			return;
		}
		if (s_radiusTargetField == null)
		{
			WarnRadiusFieldMissing();
			return;
		}
		if (__instance.m_minShieldRadius != num)
		{
			__instance.m_minShieldRadius = num;
		}
		if (__instance.m_maxShieldRadius != num)
		{
			__instance.m_maxShieldRadius = num;
		}
		s_radiusTargetField.SetValue(__instance, num);
	}

	[HarmonyPatch(typeof(ShieldDomeImageEffect), "Awake")]
	[HarmonyPostfix]
	private static void EffectAwakePostfix(ShieldDomeImageEffect __instance)
	{
		(float, bool) tuple = Net.Effective();
		if (!tuple.Item2)
		{
			return;
		}
		var (num, _) = tuple;
		if (!(num <= 0f))
		{
			float num2 = num + 50f;
			if (num2 > __instance.m_drawDistance)
			{
				__instance.m_drawDistance = num2;
			}
		}
	}
}
[BepInPlugin("dr0per.shieldbubble", "Shield Bubble", "1.1.0")]
public class Plugin : BaseUnityPlugin
{
	private ConfigEntry<float> _radius;

	private ConfigEntry<bool> _enabled;

	private static readonly WaitForSeconds Wait1s = new WaitForSeconds(1f);

	public static Plugin Instance { get; private set; }

	private void Awake()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		_radius = ((BaseUnityPlugin)this).Config.Bind<float>("Shield", "Radius", 30f, new ConfigDescription("Shield generator radius in meters (1-500).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 500f), (object[])null));
		_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Shield", "Enabled", true, new ConfigDescription("Enable the mod.", (AcceptableValueBase)null, (object[])null));
		Sync();
		_radius.SettingChanged += delegate
		{
			Sync();
			Net.MarkDirty();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"[ShieldBubble] Radius changed to {_radius.Value} m");
		};
		_enabled.SettingChanged += delegate
		{
			Sync();
			Net.MarkDirty();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"[ShieldBubble] Enabled changed to {_enabled.Value}");
		};
		new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID).PatchAll(typeof(Plugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)$"ShieldBubble ready: enabled={_enabled.Value}, radius={_radius.Value} m");
	}

	private void Start()
	{
		((MonoBehaviour)this).StartCoroutine(RegisterHandlersLoop());
		((MonoBehaviour)this).StartCoroutine(ClientSyncLoop());
		((MonoBehaviour)this).StartCoroutine(ServerPollLoop());
	}

	private IEnumerator RegisterHandlersLoop()
	{
		ZRoutedRpc instance;
		ZNet instance2;
		while (true)
		{
			instance = ZRoutedRpc.instance;
			instance2 = ZNet.instance;
			if (instance != null && (Object)(object)instance2 != (Object)null)
			{
				break;
			}
			yield return Wait1s;
		}
		if (instance2.IsServer())
		{
			instance.Register("SRModAck", (Action<long>)Net.ServerOnModAck);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[ShieldBubble] handlers registered (server: SRModAck)");
		}
		else
		{
			instance.Register<float, bool>("SRConfig", (Action<long, float, bool>)Net.ClientOnServerConfig);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[ShieldBubble] handlers registered (client: SRConfig)");
		}
	}

	private IEnumerator ClientSyncLoop()
	{
		float serverReadyTime = -1f;
		bool warnedNoConfig = false;
		bool wasServerReady = false;
		while (true)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null && !instance.IsServer())
			{
				ZNetPeer serverPeer = instance.GetServerPeer();
				if (serverPeer != null)
				{
					if (serverReadyTime < 0f)
					{
						serverReadyTime = Time.time;
					}
					Net.ClientSendAck(serverPeer.m_uid);
					if (!warnedNoConfig && Time.time - serverReadyTime >= 15f)
					{
						warnedNoConfig = true;
						((BaseUnityPlugin)this).Logger.LogWarning((object)"[ShieldBubble] no server config received after 15 s: is the server missing the mod, or is Enabled=false? Using local config.");
					}
				}
				else if (wasServerReady)
				{
					Net.ResetClient();
					serverReadyTime = -1f;
					warnedNoConfig = false;
					((BaseUnityPlugin)this).Logger.LogWarning((object)"[ShieldBubble] server lost connection: reset client sync (will re-ack on reconnect)");
				}
				wasServerReady = serverPeer != null;
			}
			yield return Wait1s;
		}
	}

	private IEnumerator ServerPollLoop()
	{
		while (true)
		{
			Net.ServerPollOnce(Time.time);
			yield return Wait1s;
		}
	}

	private void Sync()
	{
		ConfigStore.Radius = _radius.Value;
		ConfigStore.Enabled = _enabled.Value;
	}

	public static void Warn(string message)
	{
		Plugin instance = Instance;
		if (instance != null)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)message);
		}
	}
}