Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ForcedFriendship v0.3.1
ForcedFriendship.dll
Decompiled 17 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.Rendering; [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("ForcedFriendship")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.1.0")] [assembly: AssemblyInformationalVersion("0.3.1+ceac12ea65e5ec71605892d0a9ca796bc6a5a2fe")] [assembly: AssemblyProduct("ForcedFriendship")] [assembly: AssemblyTitle("ForcedFriendship")] [assembly: AssemblyVersion("0.3.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 ForcedFriendship { internal static class BeamColors { internal static Color For(BeamZone zone, bool colorblind) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (colorblind) { return (Color)(zone switch { BeamZone.Danger => new Color(0.84f, 0.15f, 0.2f), BeamZone.Warn => new Color(0.95f, 0.85f, 0.15f), _ => new Color(0.15f, 0.5f, 0.85f), }); } return (Color)(zone switch { BeamZone.Danger => new Color(0.85f, 0.2f, 0.16f), BeamZone.Warn => new Color(0.85f, 0.7f, 0.15f), _ => new Color(0.25f, 0.75f, 0.3f), }); } } internal class BeamRenderer : MonoBehaviour { private const float BeamHeight = 1f; private readonly Dictionary<PlayerAvatar, LineRenderer> _lines = new Dictionary<PlayerAvatar, LineRenderer>(); private readonly List<PlayerAvatar> _current = new List<PlayerAvatar>(); private readonly List<PlayerAvatar> _stale = new List<PlayerAvatar>(); private Material? _material; private void Update() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ActiveEnabled || !Plugin.BeamsEnabled.Value || !Plugin.IsInGameplay()) { HideAll(); return; } StatusHud instance = StatusHud.Instance; if ((Object)(object)instance == (Object)null) { HideAll(); return; } PlayerAvatar instance2 = PlayerAvatar.instance; bool value = Plugin.BeamsShowAll.Value; bool value2 = Plugin.BeamsAlwaysShow.Value; bool value3 = Plugin.BeamsColorblind.Value; float beamWidthWorld = Plugin.BeamWidthWorld; float beamOpacity = Plugin.BeamOpacity; _current.Clear(); int playerCount = instance.PlayerCount; for (int i = 0; i < playerCount; i++) { PlayerAvatar val = instance.AvatarAt(i); if (!((Object)(object)val == (Object)null)) { _current.Add(val); AnchorResult anchorResult = instance.AnchorAt(i); BeamZone zone = instance.ZoneAt(i); if (!DamageCalculator.ShouldDrawBeam(zone, anchorResult.HasAnchor, value2) || (!value && !((Object)(object)val == (Object)(object)instance2))) { HideLine(val); continue; } LineRenderer line = GetLine(val); Vector3 val2 = instance.PlayerPosAt(i) + Vector3.up * 1f; Vector3 val3 = new Vector3(anchorResult.X, anchorResult.Y, anchorResult.Z) + Vector3.up * 1f; ((Renderer)line).enabled = true; line.startWidth = beamWidthWorld; line.endWidth = beamWidthWorld; line.SetPosition(0, val2); line.SetPosition(1, val3); Color val4 = BeamColors.For(zone, value3); val4.a = beamOpacity; line.startColor = val4; line.endColor = val4; } } CleanupDeparted(); } private void OnDisable() { HideAll(); } private void OnDestroy() { if ((Object)(object)_material != (Object)null) { Object.Destroy((Object)(object)_material); } } private LineRenderer GetLine(PlayerAvatar pa) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_lines.TryGetValue(pa, out LineRenderer value) && (Object)(object)value != (Object)null) { return value; } GameObject val = new GameObject("FF_Beam"); val.transform.SetParent(((Component)this).transform, false); LineRenderer val2 = val.AddComponent<LineRenderer>(); ((Renderer)val2).material = BeamMaterial(); val2.positionCount = 2; val2.numCapVertices = 2; val2.numCornerVertices = 2; val2.useWorldSpace = true; val2.textureMode = (LineTextureMode)0; val2.alignment = (LineAlignment)0; ((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val2).receiveShadows = false; ((Renderer)val2).lightProbeUsage = (LightProbeUsage)0; _lines[pa] = val2; return val2; } private void HideLine(PlayerAvatar pa) { if (_lines.TryGetValue(pa, out LineRenderer value) && (Object)(object)value != (Object)null) { ((Renderer)value).enabled = false; } } private void HideAll() { foreach (LineRenderer value in _lines.Values) { if ((Object)(object)value != (Object)null) { ((Renderer)value).enabled = false; } } } private void CleanupDeparted() { _stale.Clear(); foreach (PlayerAvatar key in _lines.Keys) { if ((Object)(object)key == (Object)null || !_current.Contains(key)) { _stale.Add(key); } } foreach (PlayerAvatar item in _stale) { if (_lines.TryGetValue(item, out LineRenderer value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } _lines.Remove(item); } } private Material BeamMaterial() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if ((Object)(object)_material == (Object)null) { Shader val = Shader.Find("Sprites/Default") ?? Shader.Find("Unlit/Transparent"); _material = new Material(val); } return _material; } } internal static class CartLocator { internal static List<PhysGrabCart> FindMainCarts(List<PhysGrabCart> buffer) { buffer.Clear(); PhysGrabCart[] array = Object.FindObjectsOfType<PhysGrabCart>(); PhysGrabCart val = null; PhysGrabCart[] array2 = array; foreach (PhysGrabCart val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { if (!val2.isSmallCart) { buffer.Add(val2); } else if (val == null) { val = val2; } } } if (buffer.Count == 0 && (Object)(object)val != (Object)null) { buffer.Add(val); } return buffer; } } public readonly struct PlayerState { public readonly float X; public readonly float Y; public readonly float Z; public readonly bool Alive; public readonly bool InTruck; public PlayerState(float x, float y, float z, bool alive, bool inTruck = false) { X = x; Y = y; Z = z; Alive = alive; InTruck = inTruck; } } public readonly struct DamageSettings { public readonly bool Enabled; public readonly float SafeDistance; public readonly float BandWidth; public readonly int DamagePerBand; public DamageSettings(bool enabled, float safeDistance, float bandWidth, int damagePerBand) { Enabled = enabled; SafeDistance = safeDistance; BandWidth = bandWidth; DamagePerBand = damagePerBand; } } public enum AnchorMode { Buddy, Cart } public enum BeamZone { Safe, Warn, Danger } public readonly struct Vec3 { public readonly float X; public readonly float Y; public readonly float Z; public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; } } public readonly struct AnchorResult { public readonly float X; public readonly float Y; public readonly float Z; public readonly float Distance; public readonly bool HasAnchor; public readonly bool Safe; public static AnchorResult None => new AnchorResult(0f, 0f, 0f, 0f, hasAnchor: false); public AnchorResult(float x, float y, float z, float distance, bool hasAnchor, bool safe = false) { X = x; Y = y; Z = z; Distance = distance; HasAnchor = hasAnchor; Safe = safe; } } public static class DamageCalculator { public static float Distance(in PlayerState a, in PlayerState b) { return Distance(in a, in b, includeHeight: true); } public static float Distance(in PlayerState a, in PlayerState b, bool includeHeight) { float num = a.X - b.X; float num2 = (includeHeight ? (a.Y - b.Y) : 0f); float num3 = a.Z - b.Z; return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); } private static float Distance(in PlayerState a, in Vec3 b, bool includeHeight) { float num = a.X - b.X; float num2 = (includeHeight ? (a.Y - b.Y) : 0f); float num3 = a.Z - b.Z; return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); } public static int Band(float distance, float safeDistance, float bandWidth) { if (distance <= safeDistance) { return 0; } if (bandWidth <= 0f) { return 1; } return (int)Math.Floor((distance - safeDistance) / bandWidth) + 1; } public static AnchorResult[] ResolveAnchors(IReadOnlyList<PlayerState> players, AnchorMode mode, IReadOnlyList<Vec3> carts, bool includeHeight = true) { AnchorResult[] array = new AnchorResult[players.Count]; bool flag = mode == AnchorMode.Cart && carts != null && carts.Count > 0; for (int i = 0; i < players.Count; i++) { PlayerState a = players[i]; if (!a.Alive) { array[i] = AnchorResult.None; continue; } if (flag) { float num = float.PositiveInfinity; int index = -1; for (int j = 0; j < carts.Count; j++) { float num2 = Distance(in a, carts[j], includeHeight); if (num2 < num) { num = num2; index = j; } } Vec3 vec = carts[index]; array[i] = new AnchorResult(vec.X, vec.Y, vec.Z, num, hasAnchor: true, a.InTruck); continue; } float num3 = float.PositiveInfinity; int num4 = -1; bool flag2 = false; for (int k = 0; k < players.Count; k++) { if (k == i) { continue; } PlayerState b = players[k]; if (b.Alive) { if (!b.InTruck) { flag2 = true; } float num5 = Distance(in a, in b, includeHeight); if (num5 < num3) { num3 = num5; num4 = k; } } } if (num4 < 0) { array[i] = AnchorResult.None; continue; } bool safe = a.InTruck || !flag2; PlayerState playerState = players[num4]; array[i] = new AnchorResult(playerState.X, playerState.Y, playerState.Z, num3, hasAnchor: true, safe); } return array; } public static BeamZone Classify(float distance, float safeDistance, float warnPercent) { if (distance > safeDistance) { return BeamZone.Danger; } if (warnPercent <= 0f) { return BeamZone.Safe; } float num = ((warnPercent > 1f) ? 1f : warnPercent); float num2 = safeDistance * (1f - num); if (!(distance >= num2)) { return BeamZone.Safe; } return BeamZone.Warn; } public static int[] EvaluateDamage(IReadOnlyList<AnchorResult> anchors, in DamageSettings s) { int[] array = new int[anchors.Count]; if (!s.Enabled) { return array; } for (int i = 0; i < anchors.Count; i++) { AnchorResult anchorResult = anchors[i]; if (anchorResult.HasAnchor && !anchorResult.Safe) { int num = Band(anchorResult.Distance, s.SafeDistance, s.BandWidth); array[i] = num * s.DamagePerBand; } } return array; } public static BeamZone ZoneForAnchor(in AnchorResult a, float safeDistance, float warnPercent) { if (!a.Safe) { return Classify(a.Distance, safeDistance, warnPercent); } return BeamZone.Safe; } public static bool ShouldDrawBeam(BeamZone zone, bool hasAnchor, bool alwaysShow) { if (hasAnchor) { if (!alwaysShow) { return zone != BeamZone.Safe; } return true; } return false; } public static int[] Evaluate(IReadOnlyList<PlayerState> players, in DamageSettings s) { return EvaluateDamage(ResolveAnchors(players, AnchorMode.Buddy, Array.Empty<Vec3>()), in s); } } internal class ForcedFriendshipDriver : MonoBehaviour { private float _accum; private readonly List<PlayerState> _states = new List<PlayerState>(); private readonly List<PlayerAvatar> _avatars = new List<PlayerAvatar>(); private readonly List<PhysGrabCart> _carts = new List<PhysGrabCart>(); private readonly List<Vec3> _cartPositions = new List<Vec3>(); private void Update() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ActiveEnabled || (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) || !Plugin.IsInGameplay()) { return; } _accum += Time.deltaTime; if (_accum < (float)Plugin.ActiveTickInterval) { return; } _accum = 0f; List<PlayerAvatar> list = GameDirector.instance?.PlayerList; if (list == null) { return; } _states.Clear(); _avatars.Clear(); foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { Vector3 position = ((Component)item).transform.position; bool alive = PlayerLiveness.IsAlive(item); bool inTruck = PlayerLiveness.IsInTruck(item); _states.Add(new PlayerState(position.x, position.y, position.z, alive, inTruck)); _avatars.Add(item); } } DamageSettings s = new DamageSettings(enabled: true, Plugin.ActiveSafeDistance, Plugin.ActiveBandWidth, Plugin.ActiveDamagePerBand); _cartPositions.Clear(); if (Plugin.ActiveMode == AnchorMode.Cart) { CartLocator.FindMainCarts(_carts); foreach (PhysGrabCart cart in _carts) { if (!((Object)(object)cart == (Object)null)) { Vector3 position2 = ((Component)cart).transform.position; _cartPositions.Add(new Vec3(position2.x, position2.y, position2.z)); } } } int[] array = DamageCalculator.EvaluateDamage(DamageCalculator.ResolveAnchors(_states, Plugin.ActiveMode, _cartPositions, Plugin.ActiveIncludeHeight), in s); StatusHud instance = StatusHud.Instance; for (int i = 0; i < array.Length; i++) { if (array[i] > 0) { PlayerAvatar val = _avatars[i]; if ((!((Object)(object)instance != (Object)null) || !instance.IsInGrace(val)) && !((Object)(object)val.playerHealth == (Object)null)) { val.playerHealth.HurtOther(array[i], Vector3.zero, false, -1, false); } } } } } internal static class PlayerLiveness { private static readonly FieldRef<PlayerAvatar, bool> DeadSetRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("deadSet"); private static readonly FieldRef<PlayerAvatar, bool> IsDisabledRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isDisabled"); private static readonly FieldRef<RoomVolumeCheck, bool> InTruckRef = AccessTools.FieldRefAccess<RoomVolumeCheck, bool>("inTruck"); internal static bool IsAlive(PlayerAvatar pa) { if (!DeadSetRef.Invoke(pa)) { return !IsDisabledRef.Invoke(pa); } return false; } internal static bool IsInTruck(PlayerAvatar pa) { RoomVolumeCheck roomVolumeCheck = pa.RoomVolumeCheck; if ((Object)(object)roomVolumeCheck != (Object)null) { return InTruckRef.Invoke(roomVolumeCheck); } return false; } } [BepInPlugin("darkharasho.ForcedFriendship", "ForcedFriendship", "0.3.1")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> SafeDistance; internal static ConfigEntry<int> BandWidth; internal static ConfigEntry<int> DamagePerBand; internal static ConfigEntry<int> TickInterval; internal static ConfigEntry<bool> IncludeHeight; internal static ConfigEntry<int> GracePeriod; internal static ConfigEntry<AnchorMode> Mode; internal static ConfigEntry<bool> BeamsEnabled; internal static ConfigEntry<bool> BeamsShowAll; internal static ConfigEntry<bool> BeamsAlwaysShow; internal static ConfigEntry<int> BeamsWarnPercent; internal static ConfigEntry<int> BeamsWidth; internal static ConfigEntry<int> BeamsOpacity; internal static ConfigEntry<bool> BeamsColorblind; internal static ConfigEntry<bool> StatusIndicator; internal static bool ActiveEnabled; internal static AnchorMode ActiveMode; internal static int ActiveSafeDistance; internal static int ActiveBandWidth; internal static int ActiveDamagePerBand; internal static int ActiveTickInterval; internal static bool ActiveIncludeHeight; internal static int ActiveGracePeriod; internal static float BeamWidthWorld => (float)BeamsWidth.Value * 0.01f; internal static float WarnFraction => (float)BeamsWarnPercent.Value / 100f; internal static float BeamOpacity => (float)BeamsOpacity.Value / 100f; private void Awake() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Expected O, but got Unknown //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Expected O, but got Unknown //IL_02a9: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master on/off switch for Forced Friendship."); SafeDistance = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SafeDistance", 20, new ConfigDescription("Units within which your anchor (nearest player or cart) keeps you safe.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); BandWidth = ((BaseUnityPlugin)this).Config.Bind<int>("General", "BandWidth", 8, new ConfigDescription("Units per additional damage band beyond the safe radius.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); DamagePerBand = ((BaseUnityPlugin)this).Config.Bind<int>("General", "DamagePerBand", 5, new ConfigDescription("HP per tick, multiplied by the band number.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); TickInterval = ((BaseUnityPlugin)this).Config.Bind<int>("General", "TickInterval", 2, new ConfigDescription("Seconds between damage evaluations.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>())); IncludeHeight = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeHeight", false, "If true, vertical distance counts toward the safe radius. Default false so being on a different floor of the same tall room doesn't trigger damage."); GracePeriod = ((BaseUnityPlugin)this).Config.Bind<int>("General", "GracePeriod", 30, new ConfigDescription("Seconds of safety after leaving the truck before damage can start. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), Array.Empty<object>())); Mode = ((BaseUnityPlugin)this).Config.Bind<AnchorMode>("General", "AnchorMode", AnchorMode.Buddy, "Buddy = stay near the nearest living player (default). Cart = stay near the nearest hauling cart instead."); BeamsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Beams", "Enabled", true, "Draw a tether beam from each player to their anchor."); BeamsShowAll = ((BaseUnityPlugin)this).Config.Bind<bool>("Beams", "ShowAllPlayers", true, "Show beams for every living player. If false, only your own beam is drawn."); BeamsAlwaysShow = ((BaseUnityPlugin)this).Config.Bind<bool>("Beams", "AlwaysShow", true, "Always draw the tether (color conveys safety). If false, the beam hides while you're safe and only appears in the warn/danger zone."); BeamsWarnPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Beams", "WarnPercent", 25, new ConfigDescription("Percent of SafeDistance, at the outer edge, where the beam turns yellow before it turns red. 0 disables the yellow zone.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); BeamsWidth = ((BaseUnityPlugin)this).Config.Bind<int>("Beams", "Width", 2, new ConfigDescription("Tether thickness (1 = thinnest). The default approximates the game's grab beam.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>())); BeamsOpacity = ((BaseUnityPlugin)this).Config.Bind<int>("Beams", "Opacity", 40, new ConfigDescription("Beam opacity percent (1 = faint, 100 = solid). Lower is more translucent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); BeamsColorblind = ((BaseUnityPlugin)this).Config.Bind<bool>("Beams", "Colorblind", false, "Use a colorblind-friendly palette (blue safe / yellow warn / red danger) instead of green/yellow/red. Local to you — not synced from the host."); StatusIndicator = ((BaseUnityPlugin)this).Config.Bind<bool>("Beams", "StatusIndicator", false, "Show a subtle screen-edge border tinted by your current safety color (green/yellow/red) so you always know your status. Local to you."); ResetToLocalConfig(); new Harmony("darkharasho.ForcedFriendship").PatchAll(); Log.LogInfo((object)"ForcedFriendship v0.3.1 loaded."); ((Component)this).gameObject.AddComponent<StatusHud>(); ((Component)this).gameObject.AddComponent<ForcedFriendshipDriver>(); ((Component)this).gameObject.AddComponent<BeamRenderer>(); ((Component)this).gameObject.AddComponent<SettingsSyncer>(); } internal static void ResetToLocalConfig() { ActiveEnabled = Enabled.Value; ActiveMode = Mode.Value; ActiveSafeDistance = SafeDistance.Value; ActiveBandWidth = BandWidth.Value; ActiveDamagePerBand = DamagePerBand.Value; ActiveTickInterval = TickInterval.Value; ActiveIncludeHeight = IncludeHeight.Value; ActiveGracePeriod = GracePeriod.Value; } internal static bool IsInGameplay() { if (!PhotonNetwork.InRoom) { return false; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null) { return false; } Level levelCurrent = instance.levelCurrent; if ((Object)(object)levelCurrent == (Object)null) { return false; } if ((Object)(object)levelCurrent == (Object)(object)instance.levelLobby || (Object)(object)levelCurrent == (Object)(object)instance.levelLobbyMenu) { return false; } if (SemiFunc.IsLevelShop(levelCurrent)) { return false; } return true; } } internal class SettingsSyncer : MonoBehaviour { private const string K_ENABLED = "FF_EN"; private const string K_MODE = "FF_MODE"; private const string K_SAFE = "FF_SAFE"; private const string K_BAND = "FF_BAND"; private const string K_DMG = "FF_DMG"; private const string K_TICK = "FF_TICK"; private const string K_HEIGHT = "FF_HGT"; private const string K_GRACE = "FF_GRC"; internal static SettingsSyncer? Instance; private bool _wasInRoom; private float _pullPollDelay; private bool? _lastEnabled; private int _lastMode = int.MinValue; private int _lastSafe = int.MinValue; private int _lastBand = int.MinValue; private int _lastTick = int.MinValue; private int _lastDmg = int.MinValue; private bool? _lastHeight; private int _lastGrace = int.MinValue; private void Awake() { Instance = this; } private void Start() { Plugin.Log.LogInfo((object)"[Sync] SettingsSyncer ready (polling mode)"); } private void Update() { bool inRoom = PhotonNetwork.InRoom; if (inRoom && PhotonNetwork.IsMasterClient) { PushHostSettings(); } else if (inRoom) { if (!_wasInRoom) { PullHostSettings(); _pullPollDelay = 1f; } else { _pullPollDelay -= Time.unscaledDeltaTime; if (_pullPollDelay <= 0f) { _pullPollDelay = 1f; PullHostSettings(); } } } else { if (_wasInRoom) { Plugin.Log.LogInfo((object)"[Sync] Left room"); } Plugin.ResetToLocalConfig(); } _wasInRoom = inRoom; } private void PullHostSettings() { Room currentRoom = PhotonNetwork.CurrentRoom; Hashtable val = ((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null); if (val == null) { return; } bool flag = false; if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_EN")) { bool flag2 = (bool)val[(object)"FF_EN"]; if (Plugin.ActiveEnabled != flag2) { Plugin.ActiveEnabled = flag2; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_MODE")) { AnchorMode anchorMode = (AnchorMode)(int)val[(object)"FF_MODE"]; if (Plugin.ActiveMode != anchorMode) { Plugin.ActiveMode = anchorMode; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_SAFE")) { int num = (int)val[(object)"FF_SAFE"]; if (Plugin.ActiveSafeDistance != num) { Plugin.ActiveSafeDistance = num; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_BAND")) { int num2 = (int)val[(object)"FF_BAND"]; if (Plugin.ActiveBandWidth != num2) { Plugin.ActiveBandWidth = num2; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_DMG")) { int num3 = (int)val[(object)"FF_DMG"]; if (Plugin.ActiveDamagePerBand != num3) { Plugin.ActiveDamagePerBand = num3; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_TICK")) { int num4 = (int)val[(object)"FF_TICK"]; if (Plugin.ActiveTickInterval != num4) { Plugin.ActiveTickInterval = num4; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_HGT")) { bool flag3 = (bool)val[(object)"FF_HGT"]; if (Plugin.ActiveIncludeHeight != flag3) { Plugin.ActiveIncludeHeight = flag3; flag = true; } } if (((Dictionary<object, object>)(object)val).ContainsKey((object)"FF_GRC")) { int num5 = (int)val[(object)"FF_GRC"]; if (Plugin.ActiveGracePeriod != num5) { Plugin.ActiveGracePeriod = num5; flag = true; } } if (flag) { Plugin.Log.LogInfo((object)$"[Sync] Pulled host rule — enabled={Plugin.ActiveEnabled} mode={Plugin.ActiveMode} safe={Plugin.ActiveSafeDistance} band={Plugin.ActiveBandWidth} dmg={Plugin.ActiveDamagePerBand} tick={Plugin.ActiveTickInterval}"); } } private void PushHostSettings() { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Expected O, but got Unknown if (PhotonNetwork.CurrentRoom != null) { bool value = Plugin.Enabled.Value; int value2 = (int)Plugin.Mode.Value; int value3 = Plugin.SafeDistance.Value; int value4 = Plugin.BandWidth.Value; int value5 = Plugin.TickInterval.Value; int value6 = Plugin.DamagePerBand.Value; bool value7 = Plugin.IncludeHeight.Value; int value8 = Plugin.GracePeriod.Value; if (value == _lastEnabled && value2 == _lastMode && value3 == _lastSafe && value4 == _lastBand && value6 == _lastDmg && value5 == _lastTick && value7 == _lastHeight && value8 == _lastGrace) { Plugin.ResetToLocalConfig(); return; } _lastEnabled = value; _lastMode = value2; _lastSafe = value3; _lastBand = value4; _lastDmg = value6; _lastTick = value5; _lastHeight = value7; _lastGrace = value8; Hashtable val = new Hashtable { [(object)"FF_EN"] = value, [(object)"FF_MODE"] = value2, [(object)"FF_SAFE"] = value3, [(object)"FF_BAND"] = value4, [(object)"FF_DMG"] = value6, [(object)"FF_TICK"] = value5, [(object)"FF_HGT"] = value7, [(object)"FF_GRC"] = value8 }; PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); Plugin.ResetToLocalConfig(); Plugin.Log.LogInfo((object)$"[Sync] Host pushed rule — enabled={value} mode={(AnchorMode)value2} safe={value3} band={value4} dmg={value6} tick={value5} height={value7} grace={value8}"); } } } internal class StatusHud : MonoBehaviour { internal static StatusHud? Instance; private readonly Dictionary<PlayerAvatar, float> _grace = new Dictionary<PlayerAvatar, float>(); private readonly List<PlayerAvatar> _stale = new List<PlayerAvatar>(); private readonly List<PlayerState> _states = new List<PlayerState>(); private readonly List<PlayerAvatar> _avatars = new List<PlayerAvatar>(); private readonly List<PhysGrabCart> _carts = new List<PhysGrabCart>(); private readonly List<Vec3> _cartPositions = new List<Vec3>(); private float _cartRescan; private AnchorResult[] _anchors = Array.Empty<AnchorResult>(); private BeamZone[] _zones = Array.Empty<BeamZone>(); private static Texture2D? _tex; private GUIStyle? _timerStyle; internal int PlayerCount { get; private set; } internal BeamZone LocalZone { get; private set; } internal bool LocalActive { get; private set; } internal PlayerAvatar AvatarAt(int i) { return _avatars[i]; } internal AnchorResult AnchorAt(int i) { return _anchors[i]; } internal BeamZone ZoneAt(int i) { return _zones[i]; } internal Vector3 PlayerPosAt(int i) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) PlayerState playerState = _states[i]; return new Vector3(playerState.X, playerState.Y, playerState.Z); } private void Awake() { Instance = this; } internal bool IsInGrace(PlayerAvatar pa) { if (Plugin.ActiveGracePeriod > 0 && _grace.TryGetValue(pa, out var value)) { return value > 0f; } return false; } internal float Remaining(PlayerAvatar pa) { if (!_grace.TryGetValue(pa, out var value)) { return 0f; } return value; } private void Update() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ActiveEnabled || !Plugin.IsInGameplay()) { _grace.Clear(); PlayerCount = 0; LocalActive = false; return; } List<PlayerAvatar> list = GameDirector.instance?.PlayerList; if (list == null) { PlayerCount = 0; LocalActive = false; return; } float num = Plugin.ActiveGracePeriod; float deltaTime = Time.deltaTime; _states.Clear(); _avatars.Clear(); foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { bool flag = PlayerLiveness.IsInTruck(item); if (!_grace.TryGetValue(item, out var value)) { value = num; } value = (flag ? num : Mathf.Max(0f, value - deltaTime)); if (value > num) { value = num; } _grace[item] = value; Vector3 position = ((Component)item).transform.position; _states.Add(new PlayerState(position.x, position.y, position.z, PlayerLiveness.IsAlive(item), flag)); _avatars.Add(item); } } _stale.Clear(); foreach (PlayerAvatar key in _grace.Keys) { if ((Object)(object)key == (Object)null || !_avatars.Contains(key)) { _stale.Add(key); } } foreach (PlayerAvatar item2 in _stale) { _grace.Remove(item2); } AnchorMode activeMode = Plugin.ActiveMode; _cartRescan -= deltaTime; if (activeMode != AnchorMode.Cart) { _carts.Clear(); } else if (_carts.Count == 0 || _cartRescan <= 0f) { CartLocator.FindMainCarts(_carts); _cartRescan = 1f; } _cartPositions.Clear(); if (activeMode == AnchorMode.Cart) { foreach (PhysGrabCart cart in _carts) { if (!((Object)(object)cart == (Object)null)) { Vector3 position2 = ((Component)cart).transform.position; _cartPositions.Add(new Vec3(position2.x, position2.y, position2.z)); } } } _anchors = DamageCalculator.ResolveAnchors(_states, activeMode, _cartPositions, Plugin.ActiveIncludeHeight); int count = _states.Count; if (_zones.Length < count) { _zones = new BeamZone[count]; } float safeDistance = Plugin.ActiveSafeDistance; float warnFraction = Plugin.WarnFraction; for (int i = 0; i < count; i++) { BeamZone beamZone = DamageCalculator.ZoneForAnchor(in _anchors[i], safeDistance, warnFraction); if (IsInGrace(_avatars[i])) { beamZone = BeamZone.Safe; } _zones[i] = beamZone; } PlayerCount = count; LocalActive = false; PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance != (Object)null) { int num2 = _avatars.IndexOf(instance); if (num2 >= 0) { LocalZone = _zones[num2]; LocalActive = true; } } } private void OnGUI() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (!LocalActive) { return; } PlayerAvatar instance = PlayerAvatar.instance; if ((Object)(object)instance == (Object)null) { return; } if (Plugin.ActiveGracePeriod > 0 && !PlayerLiveness.IsInTruck(instance)) { float num = Remaining(instance); if (num > 0f) { DrawTimer(Mathf.CeilToInt(num)); } } if (Plugin.StatusIndicator.Value) { Color c = BeamColors.For(LocalZone, Plugin.BeamsColorblind.Value); c.a = 0.5f; DrawBorder(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), c, 4f); } } private void DrawTimer(int seconds) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (_timerStyle == null) { _timerStyle = new GUIStyle(GUI.skin.label) { fontSize = 22, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; } Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width * 0.5f - 110f, (float)Screen.height * 0.16f, 220f, 32f); Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), $"Safe for {seconds}s", _timerStyle); GUI.color = Color.white; GUI.Label(val, $"Safe for {seconds}s", _timerStyle); GUI.color = color; } private static void DrawBorder(Rect r, Color c, float t) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) Texture2D val = WhiteTex(); Color color = GUI.color; GUI.color = c; GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, ((Rect)(ref r)).width, t), (Texture)(object)val); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).yMax - t, ((Rect)(ref r)).width, t), (Texture)(object)val); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, t, ((Rect)(ref r)).height), (Texture)(object)val); GUI.DrawTexture(new Rect(((Rect)(ref r)).xMax - t, ((Rect)(ref r)).y, t, ((Rect)(ref r)).height), (Texture)(object)val); GUI.color = color; } private static Texture2D WhiteTex() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_tex == (Object)null) { _tex = new Texture2D(1, 1); _tex.SetPixel(0, 0, Color.white); _tex.Apply(); } return _tex; } } public static class PluginInfo { public const string PLUGIN_GUID = "darkharasho.ForcedFriendship"; public const string PLUGIN_NAME = "ForcedFriendship"; public const string PLUGIN_VERSION = "0.3.1"; } }