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 AllHandsOnboard v1.0.0
AllHandsOnboard.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AllHandsOnboard")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0d80bcb34d13d9ebe4ed0008a925f4e132547939")] [assembly: AssemblyProduct("AllHandsOnboard")] [assembly: AssemblyTitle("AllHandsOnboard")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } } namespace AllHandsOnboard { [BepInPlugin("com.allhands.onboard", "All hands onboard", "1.0.0")] public class Plugin : BaseUnityPlugin { private const string ModId = "com.allhands.onboard"; private const string ModName = "All hands onboard"; private const string ModVersion = "1.0.0"; public const int MaxRowers = 6; public static ManualLogSource Log; public static ConfigEntry<float> MaxMultiplierPerRower; public static ConfigEntry<float> MinMultiplier; public static ConfigEntry<float> CapRatio; public static ConfigEntry<float> DecayPerSecond; public static ConfigEntry<float> BoostPerStroke; public static ConfigEntry<float> SyncIntervalSeconds; public static ConfigEntry<float> SlotTimeoutSeconds; public static ConfigEntry<float> TargetStrokeInterval; public static ConfigEntry<float> SmoothingRate; public static ConfigEntry<float> TooFastRatio; public static ConfigEntry<float> PerfectMinRatio; public static ConfigEntry<float> PerfectMaxRatio; public static ConfigEntry<float> SlowRatio; public static ConfigEntry<float> PerfectBonusMul; public static ConfigEntry<float> GoodBonusMul; public static ConfigEntry<float> SlowBonusMul; public static ConfigEntry<float> TooFastPenaltyMul; public static ConfigEntry<int> StreakMinForBonus; public static ConfigEntry<float> StreakBonusMul; public static ConfigEntry<int> MasteryStreakMin; public static ConfigEntry<int> MasteryStreakMax; public static ConfigEntry<float> MasteryFactorAtMin; public static ConfigEntry<float> MasteryFactorAtMax; public static ConfigEntry<float> CrewSyncWindowRatio; public static ConfigEntry<float> CrewSync2; public static ConfigEntry<float> CrewSync3; public static ConfigEntry<float> CrewSync4Plus; public static ConfigEntry<float> WrongSidePenaltyMul; public static ConfigEntry<bool> ShowHud; public static ConfigEntry<bool> ShowMetronome; public static ConfigEntry<bool> MetronomeSound; public static ConfigEntry<float> MetronomeVolume; public static ConfigEntry<bool> VerboseLogs; public static ConfigEntry<RowingInput.InputMode> InputModePref; public static ConfigEntry<KeyCode> LeftKey; public static ConfigEntry<KeyCode> RightKey; public static ConfigEntry<string> LeftPadAxis; public static ConfigEntry<string> RightPadAxis; private readonly Harmony _harmony = new Harmony("com.allhands.onboard"); private void Awake() { //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_0572: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"=== All hands onboard v1.0.0 starting ==="); MaxMultiplierPerRower = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "MaxMultiplierPerRower", 1f, "Max speed multiplier each rower can contribute (at tempo=1.0)"); MinMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "MinMultiplier", 0.3f, "Base ship speed multiplier even with no rowers (vanilla = 1.0)"); CapRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "CapRatio", 0.85f, "Fraction of theoretical max sum that the speed bonus can reach"); DecayPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "DecayPerSecond", 0.6f, "How fast tempo bleeds when not rowing (per second, after grace window)"); BoostPerStroke = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "BoostPerStroke", 0.25f, "Base tempo boost per stroke (multiplied by verdict bonus and streak)"); SyncIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SyncIntervalSeconds", 0.2f, "How often local tempo is replicated to other clients via ZDO"); SlotTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SlotTimeoutSeconds", 3f, "After this many seconds with no sync, slot is considered free"); TargetStrokeInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "TargetStrokeInterval", 0.4f, "Target time between L/R strokes (sec). PERFECT window centered on this. Metronome alternates L/R at this rate."); SmoothingRate = ((BaseUnityPlugin)this).Config.Bind<float>("Rowing", "SmoothingRate", 5f, "Speed multiplier smoothing rate (anti-bouncing). Higher = faster response, lower = smoother. ~63%% of target reached in 1/rate seconds."); TooFastRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "TooFastRatio", 0.4f, "Strokes faster than this ratio of target = TOO FAST (spam, penalty)"); PerfectMinRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectMinRatio", 0.84f, "Lower bound of PERFECT window (ratio of target). 0.84 + 1.16 = ~125ms wide PERFECT at 0.4s target."); PerfectMaxRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectMaxRatio", 1.16f, "Upper bound of PERFECT window (ratio of target)."); SlowRatio = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "SlowRatio", 1.6f, "Strokes slower than this ratio of target = SLOW (no bonus, streak reset). Between PerfectMax and this = GOOD."); PerfectBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "PerfectBonusMul", 1.8f, "Multiplier on BoostPerStroke for PERFECT verdict"); GoodBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "GoodBonusMul", 1.4f, "Multiplier on BoostPerStroke for GOOD verdict"); SlowBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "SlowBonusMul", 1f, "Multiplier on BoostPerStroke for SLOW verdict (no penalty, no reward)"); TooFastPenaltyMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "TooFastPenaltyMul", 0.3f, "Multiplier on BoostPerStroke for TOO FAST verdict (penalty)"); StreakMinForBonus = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "StreakMinForBonus", 3, "Min streak count to start getting per-stroke streak bonus"); StreakBonusMul = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "StreakBonusMul", 1.5f, "Extra multiplier on stroke boost when streak >= MinForBonus"); MasteryStreakMin = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "MasteryStreakMin", 20, "Streak required to start applying mastery factor to ship speed"); MasteryStreakMax = ((BaseUnityPlugin)this).Config.Bind<int>("Streak", "MasteryStreakMax", 50, "Streak at which mastery factor reaches maximum (plateau above)"); MasteryFactorAtMin = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "MasteryFactorAtMin", 1.15f, "Ship speed multiplier when highest streak == MasteryStreakMin (1.0 = no bonus)"); MasteryFactorAtMax = ((BaseUnityPlugin)this).Config.Bind<float>("Streak", "MasteryFactorAtMax", 1.3f, "Ship speed multiplier when highest streak >= MasteryStreakMax"); CrewSyncWindowRatio = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "WindowRatio", 0.4f, "How tight (relative to target interval) other rowers' strokes must cluster to count as 'in sync'"); CrewSync2 = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor2", 1.15f, "Speed multiplier when 2 rowers are in sync"); CrewSync3 = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor3", 1.3f, "Speed multiplier when 3 rowers are in sync"); CrewSync4Plus = ((BaseUnityPlugin)this).Config.Bind<float>("CrewSync", "BonusFor4Plus", 1.45f, "Speed multiplier when 4+ rowers are in sync"); WrongSidePenaltyMul = ((BaseUnityPlugin)this).Config.Bind<float>("Rhythm", "WrongSidePenaltyMul", 0.5f, "Multiplier on BoostPerStroke subtracted from tempo when same key pressed twice in a row (WRONG SIDE). Always applied - rowing requires alternation."); ShowHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "ShowHud", true, "Show the rowing HUD widget"); ShowMetronome = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "ShowMetronome", true, "Show L/R indicator boxes (flash on player strokes)"); MetronomeSound = ((BaseUnityPlugin)this).Config.Bind<bool>("Hud", "MetronomeSound", true, "Play short tick sound on each metronome beat"); MetronomeVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Hud", "MetronomeVolume", 0.25f, "Volume of metronome tick (0.0-1.0)"); VerboseLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogs", false, "Log per-frame events (input, ticks). Spammy - turn off when not debugging."); InputModePref = ((BaseUnityPlugin)this).Config.Bind<RowingInput.InputMode>("Input", "Mode", RowingInput.InputMode.Auto, "Input mode"); LeftKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "LeftKey", (KeyCode)276, "Keyboard: left oar"); RightKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "RightKey", (KeyCode)275, "Keyboard: right oar"); LeftPadAxis = ((BaseUnityPlugin)this).Config.Bind<string>("Input", "LeftPadAxis", "JoyAxis 9", "Pad: LT axis"); RightPadAxis = ((BaseUnityPlugin)this).Config.Bind<string>("Input", "RightPadAxis", "JoyAxis 10", "Pad: RT axis"); Log.LogInfo((object)$"Configs bound. LeftKey={LeftKey.Value}, RightKey={RightKey.Value}, MaxRowers={6}"); _harmony.PatchAll(); int num = 0; foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods()) { num++; } Log.LogInfo((object)$"Harmony PatchAll done - {num} method(s) patched"); ShipAccess.SelfTest(); Log.LogInfo((object)"=== All hands onboard ready ==="); } public static void DebugLog(string msg) { if (VerboseLogs != null && VerboseLogs.Value && Log != null) { Log.LogDebug((object)msg); } } private void OnDestroy() { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Mod cleanup starting"); } try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("UnpatchSelf failed: " + ex.Message)); } } try { ShipOarsVisual[] array = Object.FindObjectsOfType<ShipOarsVisual>(); foreach (ShipOarsVisual shipOarsVisual in array) { if ((Object)(object)shipOarsVisual != (Object)null) { Object.Destroy((Object)(object)shipOarsVisual); } } } catch (Exception ex2) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)("Cleanup oars failed: " + ex2.Message)); } } try { GameObject val = GameObject.Find("RowingTempoHud"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } catch (Exception ex3) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)("Cleanup HUD failed: " + ex3.Message)); } } try { ShipTypeConfig.ClearAll(); } catch { } try { ShipRowingManager.ClearAllCache(); } catch { } ManualLogSource log5 = Log; if (log5 != null) { log5.LogInfo((object)"Mod cleanup complete"); } } } [HarmonyPatch(typeof(Ship), "GetSailForce")] public static class GetSailForcePatch { private static int _logCounter; private static void Postfix(Ship __instance, ref Vector3 __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_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 ((int)__instance.GetSpeedSetting() == 2) { float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(__instance); Vector3 val = __result; __result *= smoothedMultiplier; if (++_logCounter % 60 == 0) { Plugin.DebugLog($"[GetSailForce] Slow mode on '{((Object)((Component)__instance).gameObject).name}', mult={smoothedMultiplier:F2}, force={((Vector3)(ref val)).magnitude:F1}->{((Vector3)(ref __result)).magnitude:F1}"); } } } } [HarmonyPatch(typeof(Hud), "Update")] public static class HudPatch { private static GameObject _hudRoot; private static Image _metronomeLeft; private static Image _metronomeRight; private static Text _streakLabel; private static Text _multiplierLabel; private static Text _verdictLabel; private static bool _built; private static float _lastMetronomePhase = -1f; private static void Postfix(Hud __instance) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Invalid comparison between Unknown and I4 RowingInput.PollEdges(); if (!Plugin.ShowHud.Value) { if ((Object)(object)_hudRoot != (Object)null) { _hudRoot.SetActive(false); } return; } Player localPlayer = Player.m_localPlayer; Ship val = (((Object)(object)localPlayer != (Object)null) ? GetShipPlayerIsOn(localPlayer) : null); int num = (((Object)(object)val != (Object)null) ? ShipTypeConfig.GetFor(val) : null)?.RowerCount ?? 0; bool flag = (Object)(object)val != (Object)null && (int)val.GetSpeedSetting() == 2 && num > 0; if (!_built) { BuildHud(__instance); } if (!((Object)(object)_hudRoot == (Object)null)) { _hudRoot.SetActive(flag); if (flag) { UpdateStreakAndMultiplier(val, localPlayer); UpdateVerdictLabel(); UpdateMetronome(); } } } private static void UpdateStreakAndMultiplier(Ship ship, Player local) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_014b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ship == (Object)null || (Object)(object)local == (Object)null) { return; } int streak = LocalRowingState.Streak; if ((Object)(object)_streakLabel != (Object)null) { if (streak >= 1) { _streakLabel.text = $"STREAK x{streak}"; Color color = ((streak >= 20) ? new Color(1f, 0.85f, 0.4f, 1f) : ((streak >= 5) ? new Color(0.6f, 1f, 0.5f, 1f) : new Color(0.85f, 0.85f, 0.85f, 1f))); ((Graphic)_streakLabel).color = color; } else { _streakLabel.text = ""; } } if ((Object)(object)_multiplierLabel != (Object)null) { float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(ship); float crewSyncFactor = ShipRowingManager.GetCrewSyncFactor(ship); float masteryFactor = ShipRowingManager.GetMasteryFactor(ship); string text = ""; if (crewSyncFactor > 1.01f) { text += " SYNC"; } if (masteryFactor > 1.01f) { text += " MASTER"; } _multiplierLabel.text = $"{smoothedMultiplier:F2}x{text}"; ((Graphic)_multiplierLabel).color = (Color)((crewSyncFactor > 1.01f || masteryFactor > 1.01f) ? new Color(1f, 0.9f, 0.4f, 1f) : Color.white); } } private static void UpdateVerdictLabel() { //IL_011b: 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_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: 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_016d: Unknown result type (might be due to invalid IL or missing references) //IL_017a: 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) if ((Object)(object)_verdictLabel == (Object)null) { return; } LocalRowingState.RhythmVerdict lastVerdict = LocalRowingState.LastVerdict; float lastVerdictAge = LocalRowingState.LastVerdictAge; if (lastVerdict == LocalRowingState.RhythmVerdict.None || lastVerdictAge > 0.6f) { _verdictLabel.text = ""; return; } float a = Mathf.Clamp01(1f - lastVerdictAge / 0.6f); string text; Color val = default(Color); switch (lastVerdict) { case LocalRowingState.RhythmVerdict.Perfect: text = $"PERFECT x{LocalRowingState.Streak}"; ((Color)(ref val))..ctor(0.4f, 1f, 0.4f); break; case LocalRowingState.RhythmVerdict.Good: text = "GOOD"; ((Color)(ref val))..ctor(0.6f, 1f, 0.5f); break; case LocalRowingState.RhythmVerdict.Slow: text = "SLOW"; ((Color)(ref val))..ctor(1f, 0.85f, 0.4f); break; case LocalRowingState.RhythmVerdict.TooFast: text = "TOO FAST"; ((Color)(ref val))..ctor(1f, 0.4f, 0.4f); break; case LocalRowingState.RhythmVerdict.WrongSide: text = "WRONG SIDE"; ((Color)(ref val))..ctor(1f, 0.3f, 0.9f); break; case LocalRowingState.RhythmVerdict.First: text = "START"; val = Color.white; break; default: text = ""; val = Color.white; break; } if (lastVerdict == LocalRowingState.RhythmVerdict.Perfect && LocalRowingState.Streak >= 20) { float num = 0.7f + 0.3f * Mathf.Sin(Time.time * 12f); val = Color.Lerp(val, new Color(1f, 0.9f, 0.4f), num * 0.6f); } val.a = a; _verdictLabel.text = text; ((Graphic)_verdictLabel).color = val; } private static void UpdateMetronome() { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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_010f: 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_0111: Unknown result type (might be due to invalid IL or missing references) if (Plugin.ShowMetronome.Value) { float num = Mathf.Max(0.05f, Plugin.TargetStrokeInterval.Value); float num2 = Time.time / (num * 2f) % 1f; if (_lastMetronomePhase >= 0f) { bool flag = _lastMetronomePhase > 0.5f && num2 < 0.5f; bool flag2 = _lastMetronomePhase < 0.5f && num2 >= 0.5f; if (flag) { MetronomeAudio.PlayTick(high: true); } else if (flag2) { MetronomeAudio.PlayTick(high: false); } } _lastMetronomePhase = num2; } Color inactive = default(Color); ((Color)(ref inactive))..ctor(0.18f, 0.18f, 0.18f, 0.85f); Color flashCol = default(Color); ((Color)(ref flashCol))..ctor(0.5f, 1f, 0.5f, 1f); if ((Object)(object)_metronomeLeft != (Object)null) { ((Graphic)_metronomeLeft).color = FlashColor(LocalRowingState.LastLeftStrokeTime, 0.22f, inactive, flashCol); } if ((Object)(object)_metronomeRight != (Object)null) { ((Graphic)_metronomeRight).color = FlashColor(LocalRowingState.LastRightStrokeTime, 0.22f, inactive, flashCol); } } private static Color FlashColor(float lastStrokeTime, float duration, Color inactive, Color flashCol) { //IL_0014: 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_0021: 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) float num = Time.time - lastStrokeTime; if (num < 0f || num > duration) { return inactive; } float num2 = 1f - num / duration; return Color.Lerp(inactive, flashCol, num2); } private static Ship GetShipPlayerIsOn(Player p) { Ship controlledShip = p.GetControlledShip(); if ((Object)(object)controlledShip != (Object)null) { return controlledShip; } List<Ship> currentShips = ShipAccess.GetCurrentShips(); if (currentShips != null) { foreach (Ship item in currentShips) { if ((Object)(object)item != (Object)null && item.IsPlayerInBoat(p)) { return item; } } } else { Ship[] array = Object.FindObjectsOfType<Ship>(); foreach (Ship val in array) { if ((Object)(object)val != (Object)null && val.IsPlayerInBoat(p)) { return val; } } } return null; } private static void BuildHud(Hud hud) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0056: 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) //IL_008b: 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_00b5: 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_014d: 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_016b: 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_01a7: 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_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) _built = true; Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf"); _hudRoot = new GameObject("RowingTempoHud"); _hudRoot.transform.SetParent(((Component)hud).transform, false); Image val = _hudRoot.AddComponent<Image>(); ((Graphic)val).color = new Color(0f, 0f, 0f, 0.55f); RectTransform component = _hudRoot.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(0f, 0f); component.pivot = new Vector2(0f, 0f); component.anchoredPosition = new Vector2(16f, 240f); component.sizeDelta = new Vector2(190f, 54f); _metronomeLeft = CreateBox(_hudRoot.transform, "L_Box", "L", builtinResource, 6f, 26f, 22f); _metronomeRight = CreateBox(_hudRoot.transform, "R_Box", "R", builtinResource, 32f, 26f, 22f); _streakLabel = CreateText(_hudRoot.transform, "Streak", builtinResource, 13, bold: true, new Vector2(60f, 26f), new Vector2(124f, 22f), new Vector2(0f, 0f), (TextAnchor)3); _multiplierLabel = CreateText(_hudRoot.transform, "Multiplier", builtinResource, 18, bold: true, new Vector2(6f, 2f), new Vector2(178f, 22f), new Vector2(0f, 0f), (TextAnchor)3); GameObject val2 = new GameObject("Verdict"); val2.transform.SetParent(_hudRoot.transform, false); Text val3 = val2.AddComponent<Text>(); val3.font = builtinResource; val3.fontSize = 22; val3.fontStyle = (FontStyle)1; val3.alignment = (TextAnchor)3; val3.text = ""; Outline val4 = val2.AddComponent<Outline>(); ((Shadow)val4).effectColor = new Color(0f, 0f, 0f, 0.85f); ((Shadow)val4).effectDistance = new Vector2(1.5f, -1.5f); RectTransform component2 = val2.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(0f, 1f); component2.anchorMax = new Vector2(0f, 1f); component2.pivot = new Vector2(0f, 0f); component2.anchoredPosition = new Vector2(4f, 6f); component2.sizeDelta = new Vector2(220f, 28f); _verdictLabel = val3; } private static Image CreateBox(Transform parent, string name, string label, Font font, float x, float y, float size) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Image val2 = val.AddComponent<Image>(); ((Graphic)val2).color = new Color(0.18f, 0.18f, 0.18f, 0.85f); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(0f, 0f); component.pivot = new Vector2(0f, 0f); component.anchoredPosition = new Vector2(x, y); component.sizeDelta = new Vector2(size, size); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); Text val4 = val3.AddComponent<Text>(); val4.font = font; val4.fontSize = 14; val4.fontStyle = (FontStyle)1; val4.alignment = (TextAnchor)4; ((Graphic)val4).color = Color.white; val4.text = label; RectTransform component2 = val3.GetComponent<RectTransform>(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; return val2; } private static Text CreateText(Transform parent, string name, Font font, int fontSize, bool bold, Vector2 anchoredPos, Vector2 size, Vector2 pivot, TextAnchor align) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0038: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Text val2 = val.AddComponent<Text>(); val2.font = font; val2.fontSize = fontSize; val2.fontStyle = (FontStyle)(bold ? 1 : 0); val2.alignment = align; ((Graphic)val2).color = Color.white; val2.text = ""; RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(0f, 0f); component.pivot = pivot; component.anchoredPosition = anchoredPos; component.sizeDelta = size; return val2; } } public static class LocalRowingState { public enum RhythmVerdict { None, First, TooFast, Slow, Good, Perfect, WrongSide } private const int LeftStroke = -1; private const int RightStroke = 1; private const int NoStroke = 0; private static float _tempo; private static int _lastStroke = 0; private static float _lastStrokeTime; private static double _lastStrokeAbsTime; private static float _lastSyncTime; private static int _currentSlot = -1; private static int _streak; private static float _lastLeftStrokeTime = -10f; private static float _lastRightStrokeTime = -10f; private static RhythmVerdict _lastVerdict = RhythmVerdict.None; private static float _lastVerdictTime; private static int _tickLogCounter; public static float Tempo => _tempo; public static int CurrentSlot => _currentSlot; public static Ship CurrentShip { get; private set; } public static int Streak => _streak; public static RhythmVerdict LastVerdict => _lastVerdict; public static float LastVerdictAge => Time.time - _lastVerdictTime; public static double LastStrokeAbsTime => _lastStrokeAbsTime; public static float LastLeftStrokeTime => _lastLeftStrokeTime; public static float LastRightStrokeTime => _lastRightStrokeTime; public static void SetSlot(int slot, Ship ship) { if (_currentSlot != slot || (Object)(object)CurrentShip != (Object)(object)ship) { _currentSlot = slot; CurrentShip = ship; if (slot == -1) { _tempo = 0f; _lastStroke = 0; _streak = 0; _lastVerdict = RhythmVerdict.None; RowingInput.ClearBuffer(); } } } public static void Tick(float dt, Ship ship) { //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) if (_currentSlot == -1 || (Object)(object)ship == (Object)null) { _tempo = 0f; _lastStroke = 0; _streak = 0; RowingInput.ClearBuffer(); return; } float num = Plugin.TargetStrokeInterval.Value * 3f; if (Time.time - _lastStrokeTime > num) { _tempo = Mathf.Max(0f, _tempo - Plugin.DecayPerSecond.Value * dt); if (_streak > 0 && Time.time - _lastStrokeTime > num * 1.5f) { _streak = 0; } } if (_lastStroke != 0 && Time.time - _lastStrokeTime > Plugin.TargetStrokeInterval.Value * 4f) { _lastStroke = 0; } if (IsAnyUIBlockingInput()) { if (++_tickLogCounter % 50 == 0) { Plugin.DebugLog("[Tick] UI blocking input - clearing buffer"); } RowingInput.ClearBuffer(); return; } bool flag = RowingInput.ConsumeLeftStroke(); bool flag2 = RowingInput.ConsumeRightStroke(); if (++_tickLogCounter % 50 == 0) { Plugin.DebugLog($"[Tick] slot={_currentSlot}, mode={RowingInput.CurrentMode}, leftDown={flag}, rightDown={flag2}, lastStroke={_lastStroke}, tempo={_tempo:F2}, streak={_streak}"); } if (flag) { Plugin.DebugLog($"[Tick] LEFT consumed (KeyCode={Plugin.LeftKey.Value})"); } if (flag2) { Plugin.DebugLog($"[Tick] RIGHT consumed (KeyCode={Plugin.RightKey.Value})"); } if (flag && flag2) { if (_lastStroke == -1) { flag = false; } else { flag2 = false; } } if (flag && _lastStroke != -1) { RegisterStroke(-1, "LEFT"); } else if (flag2 && _lastStroke != 1) { RegisterStroke(1, "RIGHT"); } else if ((flag && _lastStroke == -1) || (flag2 && _lastStroke == 1)) { HandleSameKeyTwice(flag); } if (Time.time - _lastSyncTime >= Plugin.SyncIntervalSeconds.Value) { SyncToZdo(ship); _lastSyncTime = Time.time; } } private static void HandleSameKeyTwice(bool leftPressed) { float time = Time.time; float num = time - _lastStrokeTime; string arg = (leftPressed ? "LEFT" : "RIGHT"); _tempo = Mathf.Max(0f, _tempo - Plugin.BoostPerStroke.Value * Plugin.WrongSidePenaltyMul.Value); _streak = 0; _lastVerdict = RhythmVerdict.WrongSide; _lastVerdictTime = time; Plugin.DebugLog($"[Stroke] {arg} WRONG SIDE (same key 2x, gap={num:F2}s) - streak reset, tempo={_tempo:F2}"); } private static void RegisterStroke(int stroke, string sideLabel) { float time = Time.time; float num = time - _lastStrokeTime; float value = Plugin.TargetStrokeInterval.Value; float num2 = num / Mathf.Max(0.01f, value); float num3; RhythmVerdict rhythmVerdict; if (_lastStroke == 0) { num3 = 1f; rhythmVerdict = RhythmVerdict.First; } else if (num2 < Plugin.TooFastRatio.Value) { num3 = Plugin.TooFastPenaltyMul.Value; rhythmVerdict = RhythmVerdict.TooFast; } else if (num2 >= Plugin.PerfectMinRatio.Value && num2 <= Plugin.PerfectMaxRatio.Value) { num3 = Plugin.PerfectBonusMul.Value; rhythmVerdict = RhythmVerdict.Perfect; } else if (num2 < Plugin.SlowRatio.Value) { num3 = Plugin.GoodBonusMul.Value; rhythmVerdict = RhythmVerdict.Good; } else { num3 = Plugin.SlowBonusMul.Value; rhythmVerdict = RhythmVerdict.Slow; } switch (rhythmVerdict) { case RhythmVerdict.Perfect: _streak++; break; case RhythmVerdict.TooFast: case RhythmVerdict.Slow: _streak = 0; break; } float num4 = ((_streak >= Plugin.StreakMinForBonus.Value) ? Plugin.StreakBonusMul.Value : 1f); _tempo = Mathf.Min(1f, _tempo + Plugin.BoostPerStroke.Value * num3 * num4); _lastStroke = stroke; _lastStrokeTime = time; _lastStrokeAbsTime = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); switch (stroke) { case -1: _lastLeftStrokeTime = time; break; case 1: _lastRightStrokeTime = time; break; } _lastVerdict = rhythmVerdict; _lastVerdictTime = time; SendStrokeEvent(CurrentShip); string text = ((_streak >= 3) ? $" STREAK x{_streak}" : ""); Plugin.DebugLog($"[Stroke] {sideLabel} {rhythmVerdict} (gap={num:F2}s/target={value:F2}s, bonus={num3 * num4:F2}x, tempo={_tempo:F2}{text})"); } private static void SyncToZdo(Ship ship) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsValid()) { long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0); nView.InvokeRPC(ZNetView.Everybody, "Rowing_UpdateSlot", new object[4] { _currentSlot, _tempo, num, ZNet.instance.GetTimeSeconds() }); } } private static void SendStrokeEvent(Ship ship) { if (!((Object)(object)ship == (Object)null)) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsValid()) { long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0); nView.InvokeRPC(ZNetView.Everybody, "Rowing_StrokeAt", new object[4] { _currentSlot, num, _lastStrokeAbsTime, _streak }); } } } private static bool IsAnyUIBlockingInput() { try { if (InventoryGui.IsVisible()) { return true; } if (Menu.IsVisible()) { return true; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return true; } if (TextInput.IsVisible()) { return true; } if (StoreGui.IsVisible()) { return true; } if (Minimap.IsOpen()) { return true; } if ((Object)(object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible()) { return true; } } catch { } return false; } } public static class MetronomeAudio { private static AudioClip _highClip; private static AudioClip _lowClip; public static AudioClip GetClip(bool high) { if (high && (Object)(object)_highClip != (Object)null) { return _highClip; } if (!high && (Object)(object)_lowClip != (Object)null) { return _lowClip; } float num = (high ? 1320f : 880f); float[] array = new float[3675]; for (int i = 0; i < 3675; i++) { float num2 = (float)i / 44100f; float num3 = Mathf.Exp((0f - num2) * 35f); array[i] = Mathf.Sin(MathF.PI * 2f * num * num2) * num3 * 0.6f; } AudioClip val = AudioClip.Create(high ? "MetronomeHigh" : "MetronomeLow", 3675, 1, 44100, false); val.SetData(array, 0); if (high) { _highClip = val; } else { _lowClip = val; } return val; } public static void PlayTick(bool high) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (Plugin.MetronomeSound.Value) { AudioClip clip = GetClip(high); if (!((Object)(object)clip == (Object)null)) { AudioListener val = Object.FindObjectOfType<AudioListener>(); Vector3 val2 = (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : Vector3.zero); float num = Mathf.Clamp01(Plugin.MetronomeVolume.Value); AudioSource.PlayClipAtPoint(clip, val2, num); } } } } public static class RowingInput { public enum InputMode { Auto, Keyboard, Gamepad } private const float TriggerDownThreshold = 0.5f; private const float TriggerUpThreshold = 0.3f; private static bool _leftTriggerPrev; private static bool _rightTriggerPrev; private static InputMode _detectedMode = InputMode.Keyboard; private static float _lastKeyboardActivity = -10f; private static bool _bufferedLeft; private static bool _bufferedRight; public static InputMode CurrentMode { get { InputMode value = Plugin.InputModePref.Value; if (value != 0) { return value; } return _detectedMode; } } public static void UpdateDetection() { if (Input.anyKeyDown && !IsAnyMouseButtonDown()) { _detectedMode = InputMode.Keyboard; _lastKeyboardActivity = Time.time; } else if (Time.time - _lastKeyboardActivity > 5f && IsAnyGamepadInput()) { _detectedMode = InputMode.Gamepad; } } public static void PollEdges() { //IL_0012: 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) UpdateDetection(); if (CurrentMode == InputMode.Keyboard) { if (Input.GetKeyDown(Plugin.LeftKey.Value)) { _bufferedLeft = true; } if (Input.GetKeyDown(Plugin.RightKey.Value)) { _bufferedRight = true; } } else { UpdateGamepadEdges(); } } private static void UpdateGamepadEdges() { float num = SafeGetAxis(Plugin.LeftPadAxis.Value); bool flag = (_leftTriggerPrev ? (num > 0.3f) : (num > 0.5f)); if (flag && !_leftTriggerPrev) { _bufferedLeft = true; } _leftTriggerPrev = flag; float num2 = SafeGetAxis(Plugin.RightPadAxis.Value); bool flag2 = (_rightTriggerPrev ? (num2 > 0.3f) : (num2 > 0.5f)); if (flag2 && !_rightTriggerPrev) { _bufferedRight = true; } _rightTriggerPrev = flag2; } public static bool ConsumeLeftStroke() { bool bufferedLeft = _bufferedLeft; _bufferedLeft = false; return bufferedLeft; } public static bool ConsumeRightStroke() { bool bufferedRight = _bufferedRight; _bufferedRight = false; return bufferedRight; } public static void ClearBuffer() { _bufferedLeft = false; _bufferedRight = false; } private static float SafeGetAxis(string axisName) { try { return Input.GetAxisRaw(axisName); } catch { return 0f; } } private static bool IsAnyMouseButtonDown() { if (!Input.GetMouseButtonDown(0) && !Input.GetMouseButtonDown(1)) { return Input.GetMouseButtonDown(2); } return true; } private static bool IsAnyGamepadInput() { if (SafeGetAxis(Plugin.LeftPadAxis.Value) > 0.1f) { return true; } if (SafeGetAxis(Plugin.RightPadAxis.Value) > 0.1f) { return true; } for (int i = 0; i < 20; i++) { if (Input.GetKey((KeyCode)(330 + i))) { return true; } } return false; } public static string GetCurrentBindingDescription(bool isLeft) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (CurrentMode == InputMode.Keyboard) { KeyCode val = (isLeft ? Plugin.LeftKey.Value : Plugin.RightKey.Value); return ((object)(KeyCode)(ref val)).ToString(); } if (!isLeft) { return "RT"; } return "LT"; } } public static class RowingZdoKeys { public static readonly int[] SlotTempo; public static readonly int[] SlotPlayer; public static readonly int[] SlotLastUpdate; public static readonly int[] SlotLastStroke; public static readonly int[] SlotStreak; static RowingZdoKeys() { SlotTempo = new int[6]; SlotPlayer = new int[6]; SlotLastUpdate = new int[6]; SlotLastStroke = new int[6]; SlotStreak = new int[6]; for (int i = 0; i < 6; i++) { SlotTempo[i] = StableHash($"rowing_tempo_{i}"); SlotPlayer[i] = StableHash($"rowing_player_{i}"); SlotLastUpdate[i] = StableHash($"rowing_last_{i}"); SlotLastStroke[i] = StableHash($"rowing_strokeat_{i}"); SlotStreak[i] = StableHash($"rowing_streak_{i}"); } } private static int StableHash(string s) { int num = -2128831035; for (int i = 0; i < s.Length; i++) { num = (num ^ s[i]) * 16777619; } return num; } } internal static class ShipAccess { private static readonly FieldInfo NviewField = AccessTools.Field(typeof(Ship), "m_nview"); private static readonly FieldInfo FloatColliderField = AccessTools.Field(typeof(Ship), "m_floatCollider"); private static readonly FieldInfo CurrentShipsField = AccessTools.Field(typeof(Ship), "s_currentShips"); private static readonly FieldInfo ForceField = AccessTools.Field(typeof(Ship), "m_force"); private static readonly FieldInfo BackwardForceField = AccessTools.Field(typeof(Ship), "m_backwardForce"); private static readonly FieldInfo PlayerAttachPointField = AccessTools.Field(typeof(Player), "m_attachPoint"); public static float GetForce(Ship ship) { if (!((Object)(object)ship == (Object)null) && !(ForceField == null)) { return (float)ForceField.GetValue(ship); } return 0f; } public static void SetForce(Ship ship, float v) { ForceField?.SetValue(ship, v); } public static float GetBackwardForce(Ship ship) { if (!((Object)(object)ship == (Object)null) && !(BackwardForceField == null)) { return (float)BackwardForceField.GetValue(ship); } return 0f; } public static void SetBackwardForce(Ship ship, float v) { BackwardForceField?.SetValue(ship, v); } public static ZNetView GetNView(Ship ship) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ship == (Object)null) { return null; } ZNetView val = ((!(NviewField != null)) ? ((ZNetView)null) : ((ZNetView)NviewField.GetValue(ship))); if (!((Object)(object)val != (Object)null)) { return ((Component)ship).GetComponent<ZNetView>(); } return val; } public static BoxCollider GetFloatCollider(Ship ship) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown if ((Object)(object)ship == (Object)null || FloatColliderField == null) { return null; } return (BoxCollider)FloatColliderField.GetValue(ship); } public static List<Ship> GetCurrentShips() { if (CurrentShipsField == null) { return null; } return (List<Ship>)CurrentShipsField.GetValue(null); } public static bool IsPlayerSeatedOnShip(Player p, Ship ship) { if ((Object)(object)p == (Object)null || (Object)(object)ship == (Object)null) { return false; } if (!((Character)p).IsAttached()) { return false; } object? obj = PlayerAttachPointField?.GetValue(p); Transform val = (Transform)((obj is Transform) ? obj : null); if ((Object)(object)val == (Object)null) { return false; } Transform transform = ((Component)ship).transform; Transform val2 = val; for (int i = 0; i < 16; i++) { if (!((Object)(object)val2 != (Object)null)) { break; } if ((Object)(object)val2 == (Object)(object)transform) { return true; } val2 = val2.parent; } return false; } public static void SelfTest() { Plugin.Log.LogDebug((object)"[ShipAccess] SelfTest:"); Plugin.Log.LogDebug((object)(" Ship.m_nview field : " + ((NviewField != null) ? "FOUND" : "NULL - reflection failed!"))); Plugin.Log.LogDebug((object)(" Ship.m_floatCollider field: " + ((FloatColliderField != null) ? "FOUND" : "NULL"))); Plugin.Log.LogDebug((object)(" Ship.s_currentShips field : " + ((CurrentShipsField != null) ? "FOUND" : "NULL - HUD will fall back to FindObjectsOfType"))); Plugin.Log.LogDebug((object)(" Player.m_attachPoint field: " + ((PlayerAttachPointField != null) ? "FOUND" : "NULL - seated check will return false!"))); List<FieldInfo> declaredFields = AccessTools.GetDeclaredFields(typeof(Ship)); Plugin.Log.LogDebug((object)$" Ship has {declaredFields.Count} declared fields:"); foreach (FieldInfo item in declaredFields) { Plugin.Log.LogDebug((object)(" " + item.FieldType.Name + " " + item.Name)); } List<MethodInfo> declaredMethods = AccessTools.GetDeclaredMethods(typeof(Ship)); Plugin.Log.LogDebug((object)$" Ship has {declaredMethods.Count} declared methods (looking for Update/FixedUpdate variants):"); foreach (MethodInfo item2 in declaredMethods) { string text = item2.Name.ToLowerInvariant(); if (text.Contains("update") || text.Contains("fixed") || text.Contains("tick")) { string text2 = string.Join(",", from p in item2.GetParameters() select p.ParameterType.Name); Plugin.Log.LogDebug((object)(" " + item2.ReturnType.Name + " " + item2.Name + "(" + text2 + ")")); } } } } [HarmonyPatch(typeof(Ship), "Awake")] [HarmonyPriority(200)] public static class ShipAwakeOarsPatch { private static void Postfix(Ship __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponent<ShipOarsVisual>() != (Object)null)) { ShipOarsVisual shipOarsVisual = ((Component)__instance).gameObject.AddComponent<ShipOarsVisual>(); shipOarsVisual.Init(__instance); } } } [HarmonyPatch(typeof(Ship), "Awake")] public static class ShipAwakeRpcPatch { private static void Postfix(Ship __instance) { Ship __instance2 = __instance; if ((Object)(object)__instance2 == (Object)null) { return; } ZNetView nView = ShipAccess.GetNView(__instance2); if ((Object)(object)nView == (Object)null) { Plugin.Log.LogWarning((object)("[ShipAwakeRpc] Ship '" + ((Object)((Component)__instance2).gameObject).name + "' has NO ZNetView - RPC not registered!")); return; } Plugin.Log.LogDebug((object)$"[ShipAwakeRpc] Registering RPCs on '{((Object)((Component)__instance2).gameObject).name}' (instance {((Object)__instance2).GetInstanceID()})"); nView.Register<int, float, long, double>("Rowing_UpdateSlot", (Method<int, float, long, double>)delegate(long sender, int slot, float tempo, long playerId, double time) { OnUpdateSlot(__instance2, slot, tempo, playerId, time); }); nView.Register<int, long, double, int>("Rowing_StrokeAt", (Method<int, long, double, int>)delegate(long sender, int slot, long playerId, double atTime, int streak) { OnStrokeAt(__instance2, slot, playerId, atTime, streak); }); nView.Register<int, long, double>("Rowing_ClaimSlot", (Action<long, int, long, double>)delegate(long sender, int slot, long playerId, double time) { OnClaimSlot(__instance2, slot, playerId, time); }); nView.Register<int, long>("Rowing_ReleaseSlot", (Action<long, int, long>)delegate(long sender, int slot, long playerId) { OnReleaseSlot(__instance2, slot, playerId); }); } private static void OnUpdateSlot(Ship ship, int slot, float tempo, long playerId, double time) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6) { ZDO zDO = nView.GetZDO(); long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L); if (@long == playerId) { zDO.Set(RowingZdoKeys.SlotTempo[slot], Mathf.Clamp01(tempo)); zDO.Set(RowingZdoKeys.SlotLastUpdate[slot], (float)time); } } } private static void OnStrokeAt(Ship ship, int slot, long playerId, double atTime, int streak) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6) { ZDO zDO = nView.GetZDO(); long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L); if (@long == playerId) { zDO.Set(RowingZdoKeys.SlotLastStroke[slot], (float)atTime); zDO.Set(RowingZdoKeys.SlotStreak[slot], streak, false); } } } private static void OnClaimSlot(Ship ship, int slot, long playerId, double time) { ZNetView nView = ShipAccess.GetNView(ship); if ((Object)(object)nView == (Object)null || !nView.IsOwner()) { Plugin.DebugLog("[RPC ClaimSlot] Ignored - not owner of '" + ((Object)((Component)ship).gameObject).name + "'"); } else if (slot >= 0 && slot < 6) { ZDO zDO = nView.GetZDO(); double timeSeconds = ZNet.instance.GetTimeSeconds(); float value = Plugin.SlotTimeoutSeconds.Value; long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L); float @float = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[slot], 0f); if (@long == 0L || @long == playerId || timeSeconds - (double)@float > (double)value) { Plugin.Log.LogDebug((object)$"[RPC ClaimSlot] Owner accepting: slot {slot} -> player {playerId} on '{((Object)((Component)ship).gameObject).name}'"); zDO.Set(RowingZdoKeys.SlotPlayer[slot], playerId); zDO.Set(RowingZdoKeys.SlotTempo[slot], 0f); zDO.Set(RowingZdoKeys.SlotLastUpdate[slot], (float)timeSeconds); } else { Plugin.Log.LogDebug((object)$"[RPC ClaimSlot] REJECTED: slot {slot} held by {@long}, age={timeSeconds - (double)@float:F1}s"); } } } private static void OnReleaseSlot(Ship ship, int slot, long playerId) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsOwner() && slot >= 0 && slot < 6) { ZDO zDO = nView.GetZDO(); long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L); if (@long == playerId) { zDO.Set(RowingZdoKeys.SlotPlayer[slot], 0L); zDO.Set(RowingZdoKeys.SlotTempo[slot], 0f); } } } } [HarmonyPatch(typeof(Ship), "OnDestroyed")] public static class ShipDestroyedPatch { private static void Prefix(Ship __instance) { if (!((Object)(object)__instance == (Object)null)) { ShipTypeConfig.Invalidate(__instance); ShipRowingManager.InvalidateCache(__instance); ShipOarsVisual component = ((Component)__instance).GetComponent<ShipOarsVisual>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } } } } [HarmonyPatch(typeof(Ship), "CustomFixedUpdate", new Type[] { typeof(float) })] public static class ShipFixedUpdatePatch { public class State { public float SavedForce; public float SavedBackwardForce; public bool Modified; } private static int _logCounter; private static void Prefix(Ship __instance, float fixedDeltaTime, out State __state) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 __state = new State(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return; } bool flag = __instance.IsPlayerInBoat(localPlayer); bool flag2 = ShipAccess.IsPlayerSeatedOnShip(localPlayer, __instance); bool flag3 = (int)__instance.GetSpeedSetting() == 2; if (++_logCounter % 50 == 0) { Plugin.DebugLog($"[ShipTick] '{((Object)((Component)__instance).gameObject).name}' onShip={flag}, seated={flag2}, rowingMode={flag3}, mySlot={LocalRowingState.CurrentSlot}"); } if (flag3) { ShipRowingManager.TickSmoothing(__instance, fixedDeltaTime); float smoothedMultiplier = ShipRowingManager.GetSmoothedMultiplier(__instance); __state.SavedForce = ShipAccess.GetForce(__instance); __state.SavedBackwardForce = ShipAccess.GetBackwardForce(__instance); __state.Modified = true; ShipAccess.SetForce(__instance, __state.SavedForce * smoothedMultiplier); ShipAccess.SetBackwardForce(__instance, __state.SavedBackwardForce * smoothedMultiplier); if (_logCounter % 50 == 0) { float totalSpeedMultiplier = ShipRowingManager.GetTotalSpeedMultiplier(__instance); Plugin.DebugLog($"[ShipTick] Force scaled: {__state.SavedForce:F1} -> {__state.SavedForce * smoothedMultiplier:F1} (smoothed={smoothedMultiplier:F2}, raw={totalSpeedMultiplier:F2})"); } } if (!flag || !flag3 || !flag2) { if (LocalRowingState.CurrentSlot != -1 && (Object)(object)LocalRowingState.CurrentShip == (Object)(object)__instance) { Plugin.Log.LogDebug((object)$"[FixedUpdate] Releasing slot {LocalRowingState.CurrentSlot} on '{((Object)((Component)__instance).gameObject).name}' (onShip={flag}, seated={flag2}, rowing={flag3})"); ShipRowingManager.ReleaseSlot(__instance, LocalRowingState.CurrentSlot, localPlayer.GetPlayerID()); LocalRowingState.SetSlot(-1, null); } } else { if (LocalRowingState.CurrentSlot == -1) { int num = ShipRowingManager.ClaimSlot(__instance, localPlayer.GetPlayerID()); Plugin.Log.LogDebug((object)$"[FixedUpdate] Player {localPlayer.GetPlayerID()} on '{((Object)((Component)__instance).gameObject).name}' in Slow mode -> ClaimSlot returned {num}"); LocalRowingState.SetSlot(num, __instance); } LocalRowingState.Tick(fixedDeltaTime, __instance); } } private static void Postfix(Ship __instance, State __state) { if (__state != null && __state.Modified) { ShipAccess.SetForce(__instance, __state.SavedForce); ShipAccess.SetBackwardForce(__instance, __state.SavedBackwardForce); } } } public class ShipOarsVisual : MonoBehaviour { private Ship _ship; private GameObject[] _oarRoots; private Transform[] _oarShafts; private float[] _phases; private float[] _lastSeenStrokeTime; private float[] _strokePulse; private ShipTypeConfig.TypeData _typeData; private bool _built; private Material _woodMaterialBase; public void Init(Ship ship) { _ship = ship; } private void TryBuild() { if (_built || (Object)(object)_ship == (Object)null) { return; } _typeData = ShipTypeConfig.GetFor(_ship); int rowerCount = _typeData.RowerCount; _built = true; if (rowerCount != 0) { _oarRoots = (GameObject[])(object)new GameObject[rowerCount]; _oarShafts = (Transform[])(object)new Transform[rowerCount]; _phases = new float[rowerCount]; _lastSeenStrokeTime = new float[rowerCount]; _strokePulse = new float[rowerCount]; for (int i = 0; i < rowerCount; i++) { CreateOar(i); } } } private void CreateOar(int slot) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_003a: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject($"RowingOar_{slot}"); val.transform.SetParent(((Component)this).transform, false); val.transform.localPosition = _typeData.MountPoints[slot]; _oarRoots[slot] = val; GameObject val2 = GameObject.CreatePrimitive((PrimitiveType)2); ((Object)val2).name = "Shaft"; Collider component = val2.GetComponent<Collider>(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } val2.transform.SetParent(val.transform, false); float oarLength = _typeData.OarLength; float shaftRadius = _typeData.ShaftRadius; val2.transform.localScale = new Vector3(shaftRadius * 2f, oarLength * 0.5f, shaftRadius * 2f); val2.transform.localPosition = new Vector3(0f, (0f - oarLength) * 0.5f, 0f); ApplyWoodMaterial(val2, darker: false); _oarShafts[slot] = val.transform; GameObject val3 = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)val3).name = "Blade"; Collider component2 = val3.GetComponent<Collider>(); if ((Object)(object)component2 != (Object)null) { Object.Destroy((Object)(object)component2); } val3.transform.SetParent(val2.transform, false); val3.transform.localScale = new Vector3(_typeData.BladeWidth / (shaftRadius * 2f), _typeData.BladeLength / (oarLength * 0.5f), 0.05f / (shaftRadius * 2f)); val3.transform.localPosition = new Vector3(0f, -1f, 0f); ApplyWoodMaterial(val3, darker: true); bool flag = slot % 2 == 0; val.transform.localRotation = Quaternion.Euler(0f, 0f, flag ? 90f : (-90f)); } private Material EnsureWoodBase() { if ((Object)(object)_woodMaterialBase != (Object)null) { return _woodMaterialBase; } if ((Object)(object)_ship == (Object)null) { return null; } MeshRenderer[] componentsInChildren = ((Component)_ship).GetComponentsInChildren<MeshRenderer>(true); MeshRenderer[] array = componentsInChildren; foreach (MeshRenderer val in array) { if ((Object)(object)val == (Object)null || ((Object)((Component)val).gameObject).name.StartsWith("RowingOar")) { continue; } Material sharedMaterial = ((Renderer)val).sharedMaterial; if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null)) { string name = ((Object)sharedMaterial.shader).name; if (!(name == "Standard") && !(name == "Sprites/Default") && !name.Contains("Hidden") && !name.Contains("Default-Material")) { _woodMaterialBase = sharedMaterial; Plugin.DebugLog("[Oars] Using wood material from '" + ((Object)((Component)val).gameObject).name + "': shader=" + name); return _woodMaterialBase; } } } Plugin.Log.LogWarning((object)"[Oars] No usable wood material on ship - oars may render purple"); return null; } private void ApplyWoodMaterial(GameObject go, bool darker) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Renderer component = go.GetComponent<Renderer>(); if (!((Object)(object)component == (Object)null)) { Material val = EnsureWoodBase(); Material val2 = new Material(((Object)(object)val != (Object)null) ? val : component.sharedMaterial); if (val2.HasProperty("_Color")) { val2.color = (darker ? new Color(0.55f, 0.42f, 0.28f, 1f) : new Color(0.78f, 0.62f, 0.42f, 1f)); } component.material = val2; } } private void Update() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 if ((Object)(object)_ship == (Object)null) { return; } if (!_built) { TryBuild(); } if (_typeData == null || _typeData.RowerCount == 0) { return; } bool flag = (int)_ship.GetSpeedSetting() == 2; SetVisible(flag); if (flag) { ShipRowingManager.SlotData[] slots = ShipRowingManager.GetSlots(_ship); float deltaTime = Time.deltaTime; for (int i = 0; i < _typeData.RowerCount; i++) { AnimateOar(i, slots[i], deltaTime); } } } private void SetVisible(bool visible) { if (_oarRoots == null) { return; } for (int i = 0; i < _oarRoots.Length; i++) { if ((Object)(object)_oarRoots[i] != (Object)null && _oarRoots[i].activeSelf != visible) { _oarRoots[i].SetActive(visible); } } } private void AnimateOar(int slot, ShipRowingManager.SlotData data, float dt) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_0177: Unknown result type (might be due to invalid IL or missing references) Transform val = _oarShafts[slot]; if (!((Object)(object)val == (Object)null)) { bool flag = data.PlayerId != 0; float num = (flag ? data.Tempo : 0f); float num2 = (float)data.LastUpdate; if (flag && num2 > _lastSeenStrokeTime[slot] + 0.05f) { _strokePulse[slot] = 1f; _lastSeenStrokeTime[slot] = num2; Vector3 position = val.position + val.TransformDirection(Vector3.down) * _typeData.OarLength; SplashAudio.PlayAt(position); } _strokePulse[slot] = Mathf.Max(0f, _strokePulse[slot] - dt * 4f); float num3 = 0.15f + num * 1.4f; _phases[slot] += num3 * MathF.PI * 2f * dt; float num4 = Mathf.Lerp(8f, 35f, num); float num5 = Mathf.Sin(_phases[slot]) * num4; float num6 = _strokePulse[slot] * 25f * Mathf.Sin(_strokePulse[slot] * MathF.PI); bool flag2 = slot % 2 == 0; float num7 = (flag ? Mathf.Lerp(45f, 75f, num) : 90f); float num8 = (flag2 ? (-1f) : 1f) * num7; val.localRotation = Quaternion.Euler(num6, num8, flag2 ? num5 : (0f - num5)); } } private void OnDestroy() { if (_oarRoots == null) { return; } GameObject[] oarRoots = _oarRoots; foreach (GameObject val in oarRoots) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } } public static class ShipRowingManager { public class SlotData { public long PlayerId; public float Tempo; public double LastUpdate; public double LastStrokeAt; public int Streak; } private static readonly Dictionary<int, SlotData[]> _cachedSlots = new Dictionary<int, SlotData[]>(); private static readonly Dictionary<int, float> _smoothedMultiplier = new Dictionary<int, float>(); public static int GetActiveSlotCount(Ship ship) { return ShipTypeConfig.GetFor(ship).RowerCount; } public static SlotData[] GetSlots(Ship ship) { if ((Object)(object)ship == (Object)null) { return EmptySlots(); } ZNetView nView = ShipAccess.GetNView(ship); if ((Object)(object)nView == (Object)null || !nView.IsValid()) { return EmptySlots(); } ZDO zDO = nView.GetZDO(); int instanceID = ((Object)ship).GetInstanceID(); if (!_cachedSlots.TryGetValue(instanceID, out SlotData[] value)) { value = EmptySlots(); _cachedSlots[instanceID] = value; } double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); float value2 = Plugin.SlotTimeoutSeconds.Value; int activeSlotCount = GetActiveSlotCount(ship); for (int i = 0; i < 6; i++) { if (i >= activeSlotCount) { value[i].PlayerId = 0L; value[i].Tempo = 0f; value[i].LastUpdate = 0.0; value[i].LastStrokeAt = 0.0; value[i].Streak = 0; continue; } long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L); float @float = zDO.GetFloat(RowingZdoKeys.SlotTempo[i], 0f); float float2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[i], 0f); float float3 = zDO.GetFloat(RowingZdoKeys.SlotLastStroke[i], 0f); int @int = zDO.GetInt(RowingZdoKeys.SlotStreak[i], 0); if (@long == 0L || num - (double)float2 > (double)value2) { value[i].PlayerId = 0L; value[i].Tempo = 0f; value[i].LastUpdate = 0.0; value[i].LastStrokeAt = 0.0; value[i].Streak = 0; } else { value[i].PlayerId = @long; value[i].Tempo = @float; value[i].LastUpdate = float2; value[i].LastStrokeAt = float3; value[i].Streak = @int; } } return value; } public static float GetTotalSpeedMultiplier(Ship ship) { int activeSlotCount = GetActiveSlotCount(ship); if (activeSlotCount == 0) { return 1f; } SlotData[] slots = GetSlots(ship); float num = Plugin.MinMultiplier.Value; for (int i = 0; i < activeSlotCount; i++) { if (slots[i].PlayerId != 0L) { num += slots[i].Tempo * Plugin.MaxMultiplierPerRower.Value; } } float num2 = Plugin.MinMultiplier.Value + (float)activeSlotCount * Plugin.MaxMultiplierPerRower.Value * Plugin.CapRatio.Value; float num3 = Mathf.Min(num, num2); return num3 * GetCrewSyncFactor(ship) * GetMasteryFactor(ship); } public static float GetMasteryFactor(Ship ship) { if ((Object)(object)ship == (Object)null) { return 1f; } SlotData[] slots = GetSlots(ship); int activeSlotCount = GetActiveSlotCount(ship); double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); float num2 = Plugin.TargetStrokeInterval.Value * 6f; int num3 = 0; for (int i = 0; i < activeSlotCount; i++) { if (slots[i].PlayerId != 0L && !(slots[i].LastStrokeAt <= 0.0) && !(num - slots[i].LastStrokeAt > (double)num2) && slots[i].Streak > num3) { num3 = slots[i].Streak; } } int value = Plugin.MasteryStreakMin.Value; int value2 = Plugin.MasteryStreakMax.Value; float value3 = Plugin.MasteryFactorAtMin.Value; float value4 = Plugin.MasteryFactorAtMax.Value; if (num3 < value) { return 1f; } if (num3 >= value2) { return value4; } float num4 = (float)(num3 - value) / (float)Mathf.Max(1, value2 - value); return Mathf.Lerp(value3, value4, num4); } public static float GetCrewSyncFactor(Ship ship) { if ((Object)(object)ship == (Object)null) { return 1f; } int activeSlotCount = GetActiveSlotCount(ship); if (activeSlotCount < 2) { return 1f; } SlotData[] slots = GetSlots(ship); double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0); float num2 = Plugin.TargetStrokeInterval.Value * Plugin.CrewSyncWindowRatio.Value; List<double> list = new List<double>(activeSlotCount); for (int i = 0; i < activeSlotCount; i++) { if (slots[i].PlayerId != 0L && !(slots[i].LastStrokeAt <= 0.0) && !(num - slots[i].LastStrokeAt > 1.0)) { list.Add(slots[i].LastStrokeAt); } } if (list.Count < 2) { return 1f; } list.Sort(); int num3 = 1; for (int j = 0; j < list.Count; j++) { int num4 = 1; for (int k = j + 1; k < list.Count && list[k] - list[j] <= (double)num2 * 2.0; k++) { num4++; } if (num4 > num3) { num3 = num4; } } if (num3 >= 4) { return Plugin.CrewSync4Plus.Value; } return num3 switch { 3 => Plugin.CrewSync3.Value, 2 => Plugin.CrewSync2.Value, _ => 1f, }; } public static void TickSmoothing(Ship ship, float dt) { if (!((Object)(object)ship == (Object)null)) { int instanceID = ((Object)ship).GetInstanceID(); float totalSpeedMultiplier = GetTotalSpeedMultiplier(ship); if (!_smoothedMultiplier.TryGetValue(instanceID, out var value)) { _smoothedMultiplier[instanceID] = totalSpeedMultiplier; return; } float num = 1f - Mathf.Exp((0f - dt) * Plugin.SmoothingRate.Value); _smoothedMultiplier[instanceID] = Mathf.Lerp(value, totalSpeedMultiplier, num); } } public static float GetSmoothedMultiplier(Ship ship) { if ((Object)(object)ship == (Object)null) { return 1f; } int instanceID = ((Object)ship).GetInstanceID(); if (_smoothedMultiplier.TryGetValue(instanceID, out var value)) { return value; } float totalSpeedMultiplier = GetTotalSpeedMultiplier(ship); _smoothedMultiplier[instanceID] = totalSpeedMultiplier; return totalSpeedMultiplier; } public static int ClaimSlot(Ship ship, long playerId) { if ((Object)(object)ship == (Object)null) { return -1; } ZNetView nView = ShipAccess.GetNView(ship); if ((Object)(object)nView == (Object)null || !nView.IsValid()) { return -1; } int activeSlotCount = GetActiveSlotCount(ship); if (activeSlotCount == 0) { return -1; } ZDO zDO = nView.GetZDO(); double timeSeconds = ZNet.instance.GetTimeSeconds(); float value = Plugin.SlotTimeoutSeconds.Value; for (int i = 0; i < activeSlotCount; i++) { if (zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L) == playerId) { return i; } } for (int j = 0; j < activeSlotCount; j++) { long @long = zDO.GetLong(RowingZdoKeys.SlotPlayer[j], 0L); float @float = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[j], 0f); if (@long == 0L || timeSeconds - (double)@float > (double)value) { Plugin.Log.LogDebug((object)$"[ClaimSlot] Player {playerId} claiming slot {j} on '{((Object)((Component)ship).gameObject).name}' (was pid={@long}, age={timeSeconds - (double)@float:F1}s)"); nView.InvokeRPC(ZNetView.Everybody, "Rowing_ClaimSlot", new object[3] { j, playerId, timeSeconds }); return j; } } Plugin.Log.LogWarning((object)$"[ClaimSlot] Player {playerId} - no free slot on '{((Object)((Component)ship).gameObject).name}' (max={activeSlotCount})"); return -1; } public static void ReleaseSlot(Ship ship, int slot, long playerId) { if (!((Object)(object)ship == (Object)null) && slot >= 0 && slot < 6) { ZNetView nView = ShipAccess.GetNView(ship); if (!((Object)(object)nView == (Object)null) && nView.IsValid()) { nView.InvokeRPC(ZNetView.Everybody, "Rowing_ReleaseSlot", new object[2] { slot, playerId }); } } } public static void InvalidateCache(Ship ship) { if (!((Object)(object)ship == (Object)null)) { int instanceID = ((Object)ship).GetInstanceID(); _cachedSlots.Remove(instanceID); _smoothedMultiplier.Remove(instanceID); } } public static void ClearAllCache() { _cachedSlots.Clear(); _smoothedMultiplier.Clear(); } private static SlotData[] EmptySlots() { SlotData[] array = new SlotData[6]; for (int i = 0; i < 6; i++) { array[i] = new SlotData(); } return array; } } public static class ShipTypeConfig { public class TypeData { public string Label; public int RowerCount; public float OarLength; public float ShaftRadius; public float BladeWidth; public float BladeLength; public Vector3[] MountPoints; } private static readonly Dictionary<int, TypeData> _cache = new Dictionary<int, TypeData>(); public static TypeData GetFor(Ship ship) { if ((Object)(object)ship == (Object)null) { return Fallback(); } int instanceID = ((Object)ship).GetInstanceID(); if (_cache.TryGetValue(instanceID, out TypeData value)) { return value; } TypeData typeData = Build(ship); _cache[instanceID] = typeData; return typeData; } public static void Invalidate(Ship ship) { if (!((Object)(object)ship == (Object)null)) { _cache.Remove(((Object)ship).GetInstanceID()); } } public static void ClearAll() { _cache.Clear(); } private static TypeData Build(Ship ship) { //IL_001b: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) string text = (((Object)((Component)ship).gameObject).name ?? "").ToLowerInvariant(); Bounds hull = EstimateHullBounds(ship); TypeData typeData = (text.Contains("raft") ? None("Raft") : (text.Contains("karve") ? MakeData("Karve", 2, hull, 2.6f, 0f) : ((!text.Contains("drakkar") && !text.Contains("ashland")) ? MakeData("Longship", 4, hull, 3.5f, 0.45f) : MakeData("Drakkar", 6, hull, 4.2f, 0.65f)))); Plugin.Log.LogDebug((object)$"[ShipTypeConfig] Built '{((Object)((Component)ship).gameObject).name}' -> type={typeData.Label}, rowers={typeData.RowerCount}, hull bounds(local)={((Bounds)(ref hull)).size}"); return typeData; } private static Bounds EstimateHullBounds(Ship ship) { //IL_0086: 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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_0025: 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) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: 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_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_016e: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) BoxCollider floatCollider = ShipAccess.GetFloatCollider(ship); if ((Object)(object)floatCollider != (Object)null) { Bounds bounds = ((Collider)floatCollider).bounds; Vector3 val = ((Component)ship).transform.InverseTransformPoint(((Bounds)(ref bounds)).center); Vector3 val2 = ((Component)ship).transform.InverseTransformVector(((Bounds)(ref bounds)).size); ((Vector3)(ref val2))..ctor(Mathf.Abs(val2.x), Mathf.Abs(val2.y), Mathf.Abs(val2.z)); return new Bounds(val, val2); } Collider[] componentsInChildren = ((Component)ship).GetComponentsInChildren<Collider>(); bool flag = true; Bounds bounds2 = default(Bounds); ((Bounds)(ref bounds2))..ctor(((Component)ship).transform.position, Vector3.zero); Collider[] array = componentsInChildren; foreach (Collider val3 in array) { if (!((Object)(object)val3 == (Object)null) && !val3.isTrigger && !((Object)val3).name.StartsWith("RowingOar")) { if (flag) { bounds2 = val3.bounds; flag = false; } else { ((Bounds)(ref bounds2)).Encapsulate(val3.bounds); } } } if (flag) { return new Bounds(Vector3.zero, new Vector3(2.5f, 1.5f, 8f)); } Vector3 val4 = ((Component)ship).transform.InverseTransformPoint(((Bounds)(ref bounds2)).center); Vector3 val5 = ((Component)ship).transform.InverseTransformVector(((Bounds)(ref bounds2)).size); ((Vector3)(ref val5))..ctor(Mathf.Abs(val5.x), Mathf.Abs(val5.y), Mathf.Abs(val5.z)); return new Bounds(val4, val5); } private static TypeData MakeData(string label, int rowerCount, Bounds hull, float oarLen, float lengthSpread) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00c6: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Max(1, rowerCount / 2); Vector3[] array = (Vector3[])(object)new Vector3[rowerCount]; float num2 = ((Bounds)(ref hull)).size.x * 0.62f; float num3 = ((Bounds)(ref hull)).center.y + ((Bounds)(ref hull)).size.y * 0.5f; float num4 = ((Bounds)(ref hull)).size.z * 0.5f * lengthSpread; for (int i = 0; i < num; i++) { float num5 = ((num == 1) ? 0.5f : ((float)i / (float)(num - 1))); float num6 = ((Bounds)(ref hull)).center.z + Mathf.Lerp(0f - num4, num4, num5); int num7 = i * 2; int num8 = i * 2 + 1; if (num7 < rowerCount) { array[num7] = new Vector3(0f - num2, num3, num6); } if (num8 < rowerCount) { array[num8] = new Vector3(num2, num3, num6); } } return new TypeData { Label = label, RowerCount = rowerCount, OarLength = oarLen, ShaftRadius = 0.06f, BladeWidth = 0.35f, BladeLength = 0.7f, MountPoints = array }; } private static TypeData None(string label) { TypeData typeData = new TypeData(); typeData.Label = label; typeData.RowerCount = 0; typeData.MountPoints = (Vector3[])(object)new Vector3[0]; return typeData; } private static TypeData Fallback() { return None("Unknown"); } } public static class SplashAudio { private static AudioClip _splashClip; public static AudioClip GetClip() { if ((Object)(object)_splashClip != (Object)null) { return _splashClip; } float[] array = new float[11025]; Random random = new Random(42); for (int i = 0; i < 11025; i++) { float num = (float)i / 11025f; float num2 = Mathf.Exp((0f - num) * 8f); float num3 = (float)(random.NextDouble() * 2.0 - 1.0); array[i] = num3 * num2 * 0.4f; } for (int j = 1; j < 11025; j++) { array[j] = (array[j] + array[j - 1]) * 0.5f; } _splashClip = AudioClip.Create("RowingSplash", 11025, 1, 44100, false); _splashClip.SetData(array, 0); return _splashClip; } public static void PlayAt(Vector3 position, float volume = 0.3f) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) AudioClip clip = GetClip(); if (!((Object)(object)clip == (Object)null)) { AudioSource.PlayClipAtPoint(clip, position, volume); } } } }