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 BoatTracking v1.0.1
BepInEx\plugins\BoatTracking.dll
Decompiled 2 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("BoatTracking")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a24d97e39e7c8896a1756e1f1d54a2836bc54e87")] [assembly: AssemblyProduct("BoatTracking")] [assembly: AssemblyTitle("BoatTracking")] [assembly: AssemblyVersion("1.0.0.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 BoatTracking { [BepInPlugin("matthb1.boattracking", "BoatTracking", "1.0.1")] public class BoatTrackingPlugin : BaseUnityPlugin { public const string GUID = "matthb1.boattracking"; public const string NAME = "BoatTracking"; public const string VERSION = "1.0.1"; public const int ProtocolVersion = 1; public const string ZdoNameKey = "BoatTracking.Name"; public const string RpcSync = "BoatTracking.Sync"; public const string RpcRename = "BoatTracking.Rename"; public const string RpcRequestSync = "BoatTracking.RequestSync"; internal static BoatTrackingPlugin Instance; internal static ConfigEntry<bool> ShowPins; internal static ConfigEntry<bool> ShowUnnamed; internal static ConfigEntry<float> SyncInterval; internal static ConfigEntry<int> MaxNameLength; internal static ManualLogSource Log; private void Awake() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ShowPins = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowPins", true, "Show ship pins on the minimap and world map."); ShowUnnamed = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowUnnamed", true, "Show pins for ships that have not been renamed (label = ship type)."); SyncInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SyncInterval", 3f, "Seconds between server ship-position syncs (1-30)."); MaxNameLength = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxNameLength", 24, "Max characters for a ship name (1-40)."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"BoatTracking 1.0.1 loaded - name ships with hold E; server syncs map pins."); new Harmony("matthb1.boattracking").PatchAll(); } private void Update() { ShipSync.Tick(Time.deltaTime); } } internal static class ShipCatalog { internal readonly struct PrefabInfo { public readonly string Prefab; public readonly string Label; public readonly int Hash; public PrefabInfo(string prefab, string label) { Prefab = prefab; Label = label; Hash = StringExtensionMethods.GetStableHashCode(prefab); } } internal static readonly PrefabInfo[] Prefabs = new PrefabInfo[4] { new PrefabInfo("Raft", "Raft"), new PrefabInfo("Karve", "Karve"), new PrefabInfo("VikingShip", "Longship"), new PrefabInfo("VikingShip_Ashlands", "Drakkar") }; private static readonly Dictionary<int, PrefabInfo> ByHash = BuildLookup(); private static Dictionary<int, PrefabInfo> BuildLookup() { Dictionary<int, PrefabInfo> dictionary = new Dictionary<int, PrefabInfo>(); PrefabInfo[] prefabs = Prefabs; for (int i = 0; i < prefabs.Length; i++) { PrefabInfo value = prefabs[i]; dictionary[value.Hash] = value; } return dictionary; } internal static bool TryGet(int prefabHash, out PrefabInfo info) { return ByHash.TryGetValue(prefabHash, out info); } internal static string DisplayName(ZDO zdo, PrefabInfo info) { string text = zdo.GetString("BoatTracking.Name", string.Empty); if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return info.Label; } internal static string DisplayName(string custom, string typeLabel) { if (!string.IsNullOrWhiteSpace(custom)) { return custom.Trim(); } return typeLabel; } internal static Ship? FindShipFrom(Component? component) { if (!Object.op_Implicit((Object)(object)component)) { return null; } Ship val = (Ship)(object)((component is Ship) ? component : null); if (val != null) { return val; } Ship componentInParent = component.GetComponentInParent<Ship>(); if (Object.op_Implicit((Object)(object)componentInParent)) { return componentInParent; } Transform root = component.transform.root; if (!Object.op_Implicit((Object)(object)root)) { return null; } return ((Component)root).GetComponentInChildren<Ship>(); } } internal static class ShipPins { private sealed class MarkerState { public Vector3 Pos; public string Name = string.Empty; public int PrefabHash; public GameObject? Marker; } private static readonly Dictionary<ZDOID, MarkerState> Markers = new Dictionary<ZDOID, MarkerState>(); private static readonly Dictionary<int, Sprite?> SpriteCache = new Dictionary<int, Sprite>(); private static Sprite? _fallbackSprite; private static int _lastLoggedCount = -1; private static Font? _mapFont; private static bool _loggedLabelOnce; internal static void Clear() { foreach (MarkerState value in Markers.Values) { DestroyMarker(value); } Markers.Clear(); _lastLoggedCount = -1; } internal static void Apply(IReadOnlyList<ShipSnapshot> ships) { try { ApplyInternal(ships); } catch (Exception arg) { BoatTrackingPlugin.Log.LogError((object)$"ShipPins.Apply failed: {arg}"); } } private static void ApplyInternal(IReadOnlyList<ShipSnapshot> ships) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) if (!BoatTrackingPlugin.ShowPins.Value) { Clear(); return; } HashSet<ZDOID> hashSet = new HashSet<ZDOID>(); ZDOID controlledShipId = GetControlledShipId(); foreach (ShipSnapshot ship in ships) { hashSet.Add(ship.Id); if (!((ZDOID)(ref controlledShipId)).IsNone() && ship.Id == controlledShipId) { RemoveTracked(ship.Id); continue; } if (!IsSanePosition(ship.Position)) { RemoveTracked(ship.Id); continue; } string name = ShipCatalog.DisplayName(ship.Name, ship.TypeLabel); if (!Markers.TryGetValue(ship.Id, out MarkerState value)) { value = new MarkerState(); Markers[ship.Id] = value; } value.Pos = ship.Position; value.Name = name; value.PrefabHash = ship.PrefabHash; } List<ZDOID> list = new List<ZDOID>(); foreach (ZDOID key in Markers.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (ZDOID item in list) { RemoveTracked(item); } if (Markers.Count == _lastLoggedCount) { return; } _lastLoggedCount = Markers.Count; BoatTrackingPlugin.Log.LogInfo((object)$"Map markers tracked: {Markers.Count}"); foreach (KeyValuePair<ZDOID, MarkerState> marker in Markers) { BoatTrackingPlugin.Log.LogInfo((object)$" {marker.Value.Name} @ {marker.Value.Pos}"); } } internal static void UpdateDrawnMarkers() { //IL_0155: Unknown result type (might be due to invalid IL or missing references) try { if (!BoatTrackingPlugin.ShowPins.Value || !Object.op_Implicit((Object)(object)Minimap.instance) || Markers.Count == 0) { if (Markers.Count == 0) { return; } { foreach (MarkerState value in Markers.Values) { DestroyMarker(value); } return; } } Minimap instance = Minimap.instance; GameObject field = GetField<GameObject>(instance, "m_largeRoot"); RawImage field2 = GetField<RawImage>(instance, "m_mapImageLarge"); RawImage field3 = GetField<RawImage>(instance, "m_mapImageSmall"); RectTransform field4 = GetField<RectTransform>(instance, "m_pinRootLarge"); RectTransform field5 = GetField<RectTransform>(instance, "m_pinRootSmall"); GameObject field6 = GetField<GameObject>(instance, "m_pinPrefab"); if (!Object.op_Implicit((Object)(object)field) || !Object.op_Implicit((Object)(object)field2) || !Object.op_Implicit((Object)(object)field3) || !Object.op_Implicit((Object)(object)field4) || !Object.op_Implicit((Object)(object)field5) || !Object.op_Implicit((Object)(object)field6)) { return; } bool activeSelf = field.activeSelf; RawImage val = (activeSelf ? field2 : field3); RectTransform parent = (activeSelf ? field4 : field5); float size = (activeSelf ? GetField<float>(instance, "m_pinSizeLarge") : GetField<float>(instance, "m_pinSizeSmall")); foreach (MarkerState value2 in Markers.Values) { if (!IsPointVisible(value2.Pos, val, instance)) { DestroyMarker(value2); } else { DrawMarker(value2, size, parent, val, field6, activeSelf, instance); } } } catch (Exception ex) { BoatTrackingPlugin.Log.LogWarning((object)("UpdateDrawnMarkers: " + ex.Message)); } } private static void DrawMarker(MarkerState state, float size, RectTransform parent, RawImage rawImage, GameObject pinPrefab, bool large, Minimap mm) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) GameObject val = state.Marker; if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val.transform.parent != (Object)(object)parent) { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)val); } val = (state.Marker = Object.Instantiate<GameObject>(pinPrefab)); val.transform.SetParent((Transform)(object)parent, false); Image component = val.GetComponent<Image>(); if (Object.op_Implicit((Object)(object)component)) { component.sprite = ResolveSprite(state.PrefabHash) ?? component.sprite; } Transform transform = val.transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (Object.op_Implicit((Object)(object)val2)) { val2.SetSizeWithCurrentAnchors((Axis)0, size * 1.25f); val2.SetSizeWithCurrentAnchors((Axis)1, size * 1.25f); } } WorldToMapPoint(state.Pos, mm, out var mx, out var my); Vector2 anchoredPosition = MapPointToLocalGuiPos(mx, my, rawImage); Transform transform2 = val.transform; RectTransform val3 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); if (Object.op_Implicit((Object)(object)val3)) { val3.anchoredPosition = anchoredPosition; } Transform val4 = val.transform.Find("Checked"); if (Object.op_Implicit((Object)(object)val4)) { ((Component)val4).gameObject.SetActive(false); } SetMarkerName(val, state.Name, large, size); } private static void SetMarkerName(GameObject marker, string name, bool large, float pinSize) { //IL_023d: 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_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown Transform val = marker.transform.Find("Name"); if (Object.op_Implicit((Object)(object)val)) { ((Component)val).gameObject.SetActive(false); } Transform val2 = marker.transform.Find("BoatTrackingName"); Text val6; if (!Object.op_Implicit((Object)(object)val2)) { if (!_loggedLabelOnce) { _loggedLabelOnce = true; StringBuilder stringBuilder = new StringBuilder("Pin prefab children:"); foreach (Transform item in marker.transform) { Transform val3 = item; stringBuilder.Append(' ').Append(((Object)val3).name); Component[] components = ((Component)val3).GetComponents<Component>(); foreach (Component val4 in components) { if (Object.op_Implicit((Object)(object)val4)) { stringBuilder.Append('[').Append(((object)val4).GetType().Name).Append(']'); } } } BoatTrackingPlugin.Log.LogInfo((object)stringBuilder.ToString()); } GameObject val5 = new GameObject("BoatTrackingName"); val2 = val5.transform; val2.SetParent(marker.transform, false); RectTransform obj = val5.AddComponent<RectTransform>(); obj.sizeDelta = new Vector2(220f, 48f); obj.anchoredPosition = new Vector2(0f, (0f - pinSize) * 0.75f); ((Transform)obj).localScale = Vector3.one; val6 = val5.AddComponent<Text>(); val6.font = GetMapFont(); val6.fontSize = Mathf.Clamp(Mathf.RoundToInt(pinSize * 0.35f), 10, 14); val6.fontStyle = (FontStyle)1; val6.alignment = (TextAnchor)1; ((Graphic)val6).color = new Color(1f, 0.95f, 0.75f, 1f); val6.horizontalOverflow = (HorizontalWrapMode)1; val6.verticalOverflow = (VerticalWrapMode)1; ((Graphic)val6).raycastTarget = false; val6.supportRichText = false; Outline obj2 = val5.AddComponent<Outline>(); ((Shadow)obj2).effectColor = new Color(0f, 0f, 0f, 0.9f); ((Shadow)obj2).effectDistance = new Vector2(1.2f, -1.2f); } else { val6 = ((Component)val2).GetComponent<Text>(); RectTransform val7 = (RectTransform)(object)((val2 is RectTransform) ? val2 : null); if (Object.op_Implicit((Object)(object)val7)) { val7.anchoredPosition = new Vector2(0f, (0f - pinSize) * 0.75f); } } ((Component)val2).gameObject.SetActive(large && !string.IsNullOrWhiteSpace(name)); if (large && !((Object)(object)val6 == (Object)null)) { val6.text = ((Localization.instance != null) ? Localization.instance.Localize(name) : name); } } private static Font GetMapFont() { if (Object.op_Implicit((Object)(object)_mapFont)) { return _mapFont; } try { if (Object.op_Implicit((Object)(object)Minimap.instance)) { Text[] componentsInChildren = ((Component)Minimap.instance).GetComponentsInChildren<Text>(true); foreach (Text val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.font)) { _mapFont = val.font; return _mapFont; } } } } catch { } _mapFont = Resources.GetBuiltinResource<Font>("Arial.ttf"); if (!Object.op_Implicit((Object)(object)_mapFont)) { _mapFont = Font.CreateDynamicFontFromOSFont("Arial", 16); } return _mapFont; } private static Sprite? ResolveSprite(int prefabHash) { if (SpriteCache.TryGetValue(prefabHash, out Sprite value)) { return value ?? GetFallbackSprite(); } Sprite val = null; try { if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabHash); if (Object.op_Implicit((Object)(object)prefab)) { Piece component = prefab.GetComponent<Piece>(); if (Object.op_Implicit((Object)(object)component)) { object? obj = AccessTools.Field(typeof(Piece), "m_icon")?.GetValue(component); val = (Sprite)((obj is Sprite) ? obj : null); } } } } catch { } SpriteCache[prefabHash] = val; return val ?? GetFallbackSprite(); } private static Sprite? GetFallbackSprite() { if (Object.op_Implicit((Object)(object)_fallbackSprite)) { return _fallbackSprite; } try { Minimap instance = Minimap.instance; if (!Object.op_Implicit((Object)(object)instance)) { return null; } if (GetField<object>(instance, "m_icons") is IList list) { foreach (object item in list) { if (item == null) { continue; } string text = item.GetType().GetField("m_name")?.GetValue(item)?.ToString() ?? item.GetType().GetField("m_type")?.GetValue(item)?.ToString(); object? obj = item.GetType().GetField("m_icon")?.GetValue(item); Sprite val = (Sprite)(((obj is Sprite) ? obj : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); if (!((Object)(object)val == (Object)null)) { if (text != null && (text.Contains("Death") || text.Contains("Icon0"))) { _fallbackSprite = val; return _fallbackSprite; } if (_fallbackSprite == null) { _fallbackSprite = val; } } } } } catch { } return _fallbackSprite; } private static bool IsPointVisible(Vector3 p, RawImage map, Minimap mm) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) WorldToMapPoint(p, mm, out var mx, out var my); Rect uvRect = map.uvRect; if (mx > ((Rect)(ref uvRect)).xMin && mx < ((Rect)(ref uvRect)).xMax && my > ((Rect)(ref uvRect)).yMin) { return my < ((Rect)(ref uvRect)).yMax; } return false; } private static void WorldToMapPoint(Vector3 p, Minimap mm, out float mx, out float my) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) int field = GetField<int>(mm, "m_textureSize"); float field2 = GetField<float>(mm, "m_pixelSize"); int num = field / 2; mx = p.x / field2 + (float)num; my = p.z / field2 + (float)num; mx /= field; my /= field; } private static Vector2 MapPointToLocalGuiPos(float mx, float my, RawImage img) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) Rect uvRect = img.uvRect; Vector2 result = default(Vector2); ((Vector2)(ref result))..ctor((mx - ((Rect)(ref uvRect)).xMin) / ((Rect)(ref uvRect)).width, (my - ((Rect)(ref uvRect)).yMin) / ((Rect)(ref uvRect)).height); Rect rect = ((Graphic)img).rectTransform.rect; result.x *= ((Rect)(ref rect)).width; result.y *= ((Rect)(ref rect)).height; return result; } private static T? GetField<T>(object obj, string name) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name); if (fieldInfo == null) { return default(T); } object value = fieldInfo.GetValue(obj); if (value is T) { return (T)value; } if (typeof(T).IsValueType && value != null) { return (T)value; } return default(T); } private static void RemoveTracked(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (Markers.TryGetValue(id, out MarkerState value)) { DestroyMarker(value); Markers.Remove(id); } } private static void DestroyMarker(MarkerState state) { if (Object.op_Implicit((Object)(object)state.Marker)) { Object.Destroy((Object)(object)state.Marker); state.Marker = null; } } private static ZDOID GetControlledShipId() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return ZDOID.None; } Ship controlledShip = localPlayer.GetControlledShip(); if (!Object.op_Implicit((Object)(object)controlledShip)) { return ZDOID.None; } ZNetView component = ((Component)controlledShip).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid()) { return ZDOID.None; } ZDO zDO = component.GetZDO(); if (zDO == null || !zDO.IsValid()) { return ZDOID.None; } return zDO.m_uid; } private static bool IsSanePosition(Vector3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (float.IsNaN(pos.x) || float.IsNaN(pos.y) || float.IsNaN(pos.z)) { return false; } if (float.IsInfinity(pos.x) || float.IsInfinity(pos.y) || float.IsInfinity(pos.z)) { return false; } if (((Vector3)(ref pos)).sqrMagnitude < 0.01f) { return false; } return true; } } internal sealed class ShipSnapshot { public ZDOID Id; public Vector3 Position; public string Name = string.Empty; public string TypeLabel = string.Empty; public int PrefabHash; } internal static class ShipSync { private static float _timer; private static readonly List<ShipSnapshot> Latest = new List<ShipSnapshot>(); private static readonly object Gate = new object(); private static int _lastLoggedCount = -1; private static bool _loggedProtocolMismatch; internal static IReadOnlyList<ShipSnapshot> GetLatest() { lock (Gate) { return Latest.ToArray(); } } internal static void Tick(float dt) { if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null || !ZNet.instance.IsServer()) { return; } float num = Mathf.Clamp(BoatTrackingPlugin.SyncInterval.Value, 1f, 30f); _timer += dt; if (_timer < num) { return; } _timer = 0f; try { BroadcastNow(); } catch (Exception arg) { BoatTrackingPlugin.Log.LogError((object)$"Ship sync failed: {arg}"); } } internal static void BroadcastNow() { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZDOMan.instance != null) { List<ShipSnapshot> list = CollectShips(); ZPackage val = WritePackage(list); lock (Gate) { Latest.Clear(); Latest.AddRange(list); } if (list.Count != _lastLoggedCount) { _lastLoggedCount = list.Count; BoatTrackingPlugin.Log.LogInfo((object)$"Broadcasting {list.Count} ship pin(s) to clients."); } if (!ZNet.instance.IsDedicated() && Object.op_Implicit((Object)(object)Minimap.instance)) { ShipPins.Apply(list); } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "BoatTracking.Sync", new object[1] { val }); } } internal static void RequestFromServer() { if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return; } if (ZNet.instance.IsServer()) { BroadcastNow(); return; } long serverPeerIdSafe = GetServerPeerIdSafe(); if (serverPeerIdSafe != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerIdSafe, "BoatTracking.RequestSync", Array.Empty<object>()); } } internal static void HandleRequestSync(long sender) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZPackage val = WritePackage(CollectShips()); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "BoatTracking.Sync", new object[1] { val }); } } internal static void HandleSync(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsDedicated() || pkg == null || pkg.Size() == 0) { return; } try { pkg.SetPos(0); if (TryReadPackage(pkg, out List<ShipSnapshot> ships)) { lock (Gate) { Latest.Clear(); Latest.AddRange(ships); } if (ships.Count != _lastLoggedCount) { _lastLoggedCount = ships.Count; BoatTrackingPlugin.Log.LogInfo((object)$"Received {ships.Count} ship pin(s) from server."); } ShipPins.Apply(ships); } } catch (Exception arg) { BoatTrackingPlugin.Log.LogError((object)$"Ship sync receive failed: {arg}"); } } internal static void HandleRename(long sender, ZPackage pkg) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && pkg != null) { pkg.SetPos(0); ZDOID id = pkg.ReadZDOID(); string name = pkg.ReadString() ?? string.Empty; ApplyRename(id, name); } } internal static void SubmitRename(ZDOID id, string name) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) name = SanitizeName(name); if ((Object)(object)ZNet.instance == (Object)null) { return; } if (ZNet.instance.IsServer()) { ApplyRename(id, name); return; } ZPackage val = new ZPackage(); val.Write(id); val.Write(name); long serverPeerIdSafe = GetServerPeerIdSafe(); if (serverPeerIdSafe == 0L) { BoatTrackingPlugin.Log.LogWarning((object)"Rename skipped - no server peer id yet."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerIdSafe, "BoatTracking.Rename", new object[1] { val }); } private static long GetServerPeerIdSafe() { try { MethodInfo methodInfo = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null); if (methodInfo != null && ZRoutedRpc.instance != null && methodInfo.Invoke(ZRoutedRpc.instance, null) is long result) { return result; } } catch (Exception ex) { BoatTrackingPlugin.Log.LogWarning((object)("GetServerPeerID reflection failed: " + ex.Message)); } try { ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val != null) { FieldInfo fieldInfo = AccessTools.Field(((object)val).GetType(), "m_uid"); if (fieldInfo != null && fieldInfo.GetValue(val) is long result2) { return result2; } } } catch (Exception ex2) { BoatTrackingPlugin.Log.LogWarning((object)("GetServerPeer fallback failed: " + ex2.Message)); } return 0L; } private static void ApplyRename(ZDOID id, string name) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(id) : null); if (val != null && val.IsValid() && ShipCatalog.TryGet(val.GetPrefab(), out var _)) { val.SetOwner(ZDOMan.GetSessionID()); val.Set("BoatTracking.Name", name); BroadcastNow(); } } internal static string SanitizeName(string? name) { if (name == null || string.IsNullOrWhiteSpace(name)) { return string.Empty; } string text = name.Trim(); int num = Mathf.Clamp(BoatTrackingPlugin.MaxNameLength.Value, 1, 40); if (text.Length > num) { text = text.Substring(0, num); } return text; } private static List<ShipSnapshot> CollectShips() { List<ShipSnapshot> result = new List<ShipSnapshot>(); ZDOMan instance = ZDOMan.instance; if (instance == null) { return result; } HashSet<ZDOID> seen = new HashSet<ZDOID>(); CollectFromObjectsById(instance, Consider); if (result.Count == 0) { CollectFromPrefabIterative(instance, Consider); } return result; void Consider(ZDO? zdo) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (zdo != null && zdo.IsValid() && seen.Add(zdo.m_uid) && ShipCatalog.TryGet(zdo.GetPrefab(), out var info)) { string text = zdo.GetString("BoatTracking.Name", string.Empty); if (BoatTrackingPlugin.ShowUnnamed.Value || !string.IsNullOrWhiteSpace(text)) { Vector3 position = zdo.GetPosition(); result.Add(new ShipSnapshot { Id = zdo.m_uid, Position = position, Name = (text?.Trim() ?? string.Empty), TypeLabel = info.Label, PrefabHash = info.Hash }); } } } } private static void CollectFromObjectsById(ZDOMan zdoMan, Action<ZDO?> consider) { try { object obj = AccessTools.Field(typeof(ZDOMan), "m_objectsByID")?.GetValue(zdoMan); if (obj is IDictionary dictionary) { { foreach (object value2 in dictionary.Values) { ZDO val = (ZDO)((value2 is ZDO) ? value2 : null); if (val != null) { consider(val); } } return; } } if (!(obj is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { ZDO val2 = (ZDO)((item is ZDO) ? item : null); if (val2 != null) { consider(val2); } else if (item is DictionaryEntry dictionaryEntry) { object? value = dictionaryEntry.Value; ZDO val3 = (ZDO)((value is ZDO) ? value : null); if (val3 != null) { consider(val3); } } } } catch (Exception ex) { BoatTrackingPlugin.Log.LogWarning((object)("ZDO id scan failed: " + ex.Message)); } } private static void CollectFromPrefabIterative(ZDOMan zdoMan, Action<ZDO?> consider) { List<ZDO> list = new List<ZDO>(); ShipCatalog.PrefabInfo[] prefabs = ShipCatalog.Prefabs; for (int i = 0; i < prefabs.Length; i++) { ShipCatalog.PrefabInfo prefabInfo = prefabs[i]; list.Clear(); int num = 0; int num2 = 0; while (!zdoMan.GetAllZDOsWithPrefabIterative(prefabInfo.Prefab, list, ref num)) { if (++num2 > 100000) { BoatTrackingPlugin.Log.LogWarning((object)("Prefab scan aborted for " + prefabInfo.Prefab)); break; } } foreach (ZDO item in list) { consider(item); } } } private static ZPackage WritePackage(List<ShipSnapshot> ships) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); val.Write(1); val.Write(ships.Count); foreach (ShipSnapshot ship in ships) { val.Write(ship.Id); val.Write(ship.Position); val.Write(ship.Name ?? string.Empty); val.Write(ship.TypeLabel ?? string.Empty); val.Write(ship.PrefabHash); } return val; } private static bool TryReadPackage(ZPackage pkg, out List<ShipSnapshot> ships) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) ships = new List<ShipSnapshot>(); int num = pkg.ReadInt(); if (num != 1) { NotifyProtocolMismatch(num); return false; } int num2 = pkg.ReadInt(); if (num2 < 0 || num2 > 512) { BoatTrackingPlugin.Log.LogWarning((object)$"Ship sync rejected: unreasonable ship count {num2}."); return false; } ships = new List<ShipSnapshot>(num2); for (int i = 0; i < num2; i++) { ships.Add(new ShipSnapshot { Id = pkg.ReadZDOID(), Position = pkg.ReadVector3(), Name = (pkg.ReadString() ?? string.Empty), TypeLabel = (pkg.ReadString() ?? string.Empty), PrefabHash = pkg.ReadInt() }); } return true; } private static void NotifyProtocolMismatch(int serverVersion) { if (_loggedProtocolMismatch) { return; } _loggedProtocolMismatch = true; int num = 1; string text = ((serverVersion >= num) ? $"BoatTracking: your mod is outdated (server protocol {serverVersion}, you have {num}). Update BoatTracking to match the server." : $"BoatTracking: server mod is outdated (server protocol {serverVersion}, you have {num}). Ask the host to update BoatTracking."); BoatTrackingPlugin.Log.LogWarning((object)text); try { if (Object.op_Implicit((Object)(object)MessageHud.instance)) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } catch (Exception ex) { BoatTrackingPlugin.Log.LogWarning((object)("Could not show protocol mismatch HUD: " + ex.Message)); } try { if (Object.op_Implicit((Object)(object)Chat.instance)) { ((Terminal)Chat.instance).AddString("BoatTracking", text, (Type)1, false); } } catch { } } } } namespace BoatTracking.Patches { [HarmonyPatch(typeof(Player), "Interact")] internal static class InteractRenamePatch { private sealed class ShipNameReceiver : TextReceiver { private readonly ZDOID _id; public ShipNameReceiver(ZDOID id) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _id = id; } public string GetText() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(_id) : null); if (val == null || !val.IsValid()) { return string.Empty; } return val.GetString("BoatTracking.Name", string.Empty); } public void SetText(string text) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ShipSync.SubmitRename(_id, text); } } private const float RenameHoldSeconds = 0.75f; internal static bool OpenedThisHold; private static float UsePressStartedAt = -1f; private static bool SuppressTypingUntilUseReleased; private static string SeedText = string.Empty; private static bool Prefix(Player __instance, GameObject go, bool hold, bool alt) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (ShouldLeaveUseAlone(__instance, go)) { OpenedThisHold = false; return true; } if (!hold) { return true; } if (OpenedThisHold || TextInput.IsVisible()) { if (IsRenameTarget(go)) { return false; } return true; } if (!IsRenameTarget(go)) { return true; } if (UsePressStartedAt < 0f || Time.time - UsePressStartedAt < 0.75f) { return true; } Ship val = ShipCatalog.FindShipFrom(go.GetComponent<Component>()); if (!Object.op_Implicit((Object)(object)val)) { return true; } ZNetView component = ((Component)val).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid()) { return true; } if (!Object.op_Implicit((Object)(object)TextInput.instance)) { return true; } int num = Mathf.Clamp(BoatTrackingPlugin.MaxNameLength.Value, 1, 40); ShipNameReceiver shipNameReceiver = new ShipNameReceiver(component.GetZDO().m_uid); SeedText = shipNameReceiver.GetText() ?? string.Empty; SuppressTypingUntilUseReleased = true; OpenedThisHold = true; TextInput.instance.RequestText((TextReceiver)(object)shipNameReceiver, "Rename ship", num); ForceTextInputText(SeedText); return false; } internal static void OnPlayerUpdate(Player player) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } if (ZInput.GetButton("Use") || ZInput.GetButton("JoyUse")) { if (UsePressStartedAt < 0f) { UsePressStartedAt = Time.time; } if (SuppressTypingUntilUseReleased && TextInput.IsVisible()) { ForceTextInputText(SeedText); } return; } UsePressStartedAt = -1f; OpenedThisHold = false; if (SuppressTypingUntilUseReleased) { SuppressTypingUntilUseReleased = false; if (TextInput.IsVisible()) { ForceTextInputText(SeedText); } } } private static void ForceTextInputText(string text) { TextInput instance = TextInput.instance; if (!Object.op_Implicit((Object)(object)instance)) { return; } string[] array = new string[3] { "m_textField", "m_inputField", "m_input" }; foreach (string text2 in array) { FieldInfo fieldInfo = AccessTools.Field(typeof(TextInput), text2); if (fieldInfo == null) { continue; } object value = fieldInfo.GetValue(instance); if (value == null) { continue; } if (value is string) { fieldInfo.SetValue(instance, text); continue; } PropertyInfo property = value.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.CanWrite) { property.SetValue(value, text, null); break; } } } internal static bool ShouldLeaveUseAlone(Player player, GameObject? go) { if (!Object.op_Implicit((Object)(object)player)) { return false; } if (IsRudderHover(go)) { return true; } if (Object.op_Implicit((Object)(object)go) && Object.op_Implicit((Object)(object)go.GetComponentInParent<Ladder>())) { return true; } if (IsLocalPlayerSteering(player)) { return true; } return false; } internal static bool IsLocalPlayerSteering(Player player) { if (!Object.op_Implicit((Object)(object)player)) { return false; } if (Object.op_Implicit((Object)(object)player.GetControlledShip())) { return true; } IDoodadController doodadController = player.GetDoodadController(); if (doodadController != null && doodadController.IsValid()) { return doodadController.GetControlledComponent() is Ship; } return false; } internal static bool IsRudderHover(GameObject? go) { if (Object.op_Implicit((Object)(object)go)) { return Object.op_Implicit((Object)(object)go.GetComponentInParent<ShipControlls>()); } return false; } private static bool IsRenameTarget(GameObject? go) { if (!Object.op_Implicit((Object)(object)go)) { return false; } if (Object.op_Implicit((Object)(object)go.GetComponentInParent<Container>())) { return false; } if (Object.op_Implicit((Object)(object)go.GetComponentInParent<Ladder>())) { return false; } if (Object.op_Implicit((Object)(object)go.GetComponentInParent<ShipControlls>())) { return false; } return Object.op_Implicit((Object)(object)ShipCatalog.FindShipFrom(go.GetComponent<Component>())); } } [HarmonyPatch(typeof(Player), "Update")] internal static class InteractRenameHoldTrackPatch { private static void Postfix(Player __instance) { InteractRenamePatch.OnPlayerUpdate(__instance); } } [HarmonyPatch(typeof(ShipControlls), "GetHoverText")] internal static class ShipControllsHoverPatch { private static void Postfix(ShipControlls __instance, ref string __result) { HoverText.AppendShipName((Component)(object)__instance, ref __result, showRenameHint: false); } } [HarmonyPatch(typeof(Chair), "GetHoverText")] internal static class ChairHoverPatch { private static void Postfix(Chair __instance, ref string __result) { if (Object.op_Implicit((Object)(object)ShipCatalog.FindShipFrom((Component?)(object)__instance))) { bool showRenameHint = Object.op_Implicit((Object)(object)Player.m_localPlayer) && !InteractRenamePatch.ShouldLeaveUseAlone(Player.m_localPlayer, ((Component)__instance).gameObject); HoverText.AppendShipName((Component)(object)__instance, ref __result, showRenameHint); } } } internal static class HoverText { internal static void AppendShipName(Component component, ref string result, bool showRenameHint) { Ship val = ShipCatalog.FindShipFrom(component); if (!Object.op_Implicit((Object)(object)val)) { return; } ZNetView component2 = ((Component)val).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)component2) || !component2.IsValid()) { return; } ZDO zDO = component2.GetZDO(); if (ShipCatalog.TryGet(zDO.GetPrefab(), out var info)) { string text = ShipCatalog.DisplayName(zDO, info); string text2 = string.Empty; if (showRenameHint) { text2 = Localization.instance.Localize("\n[<color=yellow><b>Hold $KEY_Use</b></color>] Rename ship"); } if (string.IsNullOrEmpty(result)) { result = "<color=orange>" + text + "</color>" + text2; } else if (!result.Contains(text)) { result = "<color=orange>" + text + "</color>\n" + result + text2; } else if (text2.Length > 0 && !result.Contains("Rename ship")) { result += text2; } } } } [HarmonyPatch(typeof(Game), "Start")] internal static class GameStartPatch { private static void Prefix() { ZRoutedRpc.instance.Register("BoatTracking.RequestSync", (Action<long>)ShipSync.HandleRequestSync); ZRoutedRpc.instance.Register<ZPackage>("BoatTracking.Sync", (Action<long, ZPackage>)ShipSync.HandleSync); ZRoutedRpc.instance.Register<ZPackage>("BoatTracking.Rename", (Action<long, ZPackage>)ShipSync.HandleRename); } } [HarmonyPatch(typeof(Game), "Logout")] internal static class GameLogoutPatch { private static void Prefix() { ShipPins.Clear(); } } [HarmonyPatch(typeof(Minimap), "OnDestroy")] internal static class MinimapDestroyPatch { private static void Postfix() { ShipPins.Clear(); } } [HarmonyPatch(typeof(Minimap), "Awake")] internal static class MinimapAwakePatch { private static void Postfix() { ShipSync.RequestFromServer(); } } [HarmonyPatch(typeof(Minimap), "UpdatePins")] internal static class MinimapUpdatePinsPatch { private static void Postfix() { ShipPins.UpdateDrawnMarkers(); } } [HarmonyPatch(typeof(Minimap), "UpdateMap")] internal static class MinimapUpdateRefreshPatch { private static float _next; private static void Postfix() { if (!(Time.time < _next)) { _next = Time.time + 5f; IReadOnlyList<ShipSnapshot> latest = ShipSync.GetLatest(); if (latest.Count > 0) { ShipPins.Apply(latest); } } } } }