using 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 MaxMultiplierPerRower; public static ConfigEntry MinMultiplier; public static ConfigEntry CapRatio; public static ConfigEntry DecayPerSecond; public static ConfigEntry BoostPerStroke; public static ConfigEntry SyncIntervalSeconds; public static ConfigEntry SlotTimeoutSeconds; public static ConfigEntry TargetStrokeInterval; public static ConfigEntry SmoothingRate; public static ConfigEntry TooFastRatio; public static ConfigEntry PerfectMinRatio; public static ConfigEntry PerfectMaxRatio; public static ConfigEntry SlowRatio; public static ConfigEntry PerfectBonusMul; public static ConfigEntry GoodBonusMul; public static ConfigEntry SlowBonusMul; public static ConfigEntry TooFastPenaltyMul; public static ConfigEntry StreakMinForBonus; public static ConfigEntry StreakBonusMul; public static ConfigEntry MasteryStreakMin; public static ConfigEntry MasteryStreakMax; public static ConfigEntry MasteryFactorAtMin; public static ConfigEntry MasteryFactorAtMax; public static ConfigEntry CrewSyncWindowRatio; public static ConfigEntry CrewSync2; public static ConfigEntry CrewSync3; public static ConfigEntry CrewSync4Plus; public static ConfigEntry WrongSidePenaltyMul; public static ConfigEntry ShowHud; public static ConfigEntry ShowMetronome; public static ConfigEntry MetronomeSound; public static ConfigEntry MetronomeVolume; public static ConfigEntry VerboseLogs; public static ConfigEntry InputModePref; public static ConfigEntry LeftKey; public static ConfigEntry RightKey; public static ConfigEntry LeftPadAxis; public static ConfigEntry 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("Rowing", "MaxMultiplierPerRower", 1f, "Max speed multiplier each rower can contribute (at tempo=1.0)"); MinMultiplier = ((BaseUnityPlugin)this).Config.Bind("Rowing", "MinMultiplier", 0.3f, "Base ship speed multiplier even with no rowers (vanilla = 1.0)"); CapRatio = ((BaseUnityPlugin)this).Config.Bind("Rowing", "CapRatio", 0.85f, "Fraction of theoretical max sum that the speed bonus can reach"); DecayPerSecond = ((BaseUnityPlugin)this).Config.Bind("Rowing", "DecayPerSecond", 0.6f, "How fast tempo bleeds when not rowing (per second, after grace window)"); BoostPerStroke = ((BaseUnityPlugin)this).Config.Bind("Rowing", "BoostPerStroke", 0.25f, "Base tempo boost per stroke (multiplied by verdict bonus and streak)"); SyncIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("Rowing", "SyncIntervalSeconds", 0.2f, "How often local tempo is replicated to other clients via ZDO"); SlotTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind("Rowing", "SlotTimeoutSeconds", 3f, "After this many seconds with no sync, slot is considered free"); TargetStrokeInterval = ((BaseUnityPlugin)this).Config.Bind("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("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("Rhythm", "TooFastRatio", 0.4f, "Strokes faster than this ratio of target = TOO FAST (spam, penalty)"); PerfectMinRatio = ((BaseUnityPlugin)this).Config.Bind("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("Rhythm", "PerfectMaxRatio", 1.16f, "Upper bound of PERFECT window (ratio of target)."); SlowRatio = ((BaseUnityPlugin)this).Config.Bind("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("Rhythm", "PerfectBonusMul", 1.8f, "Multiplier on BoostPerStroke for PERFECT verdict"); GoodBonusMul = ((BaseUnityPlugin)this).Config.Bind("Rhythm", "GoodBonusMul", 1.4f, "Multiplier on BoostPerStroke for GOOD verdict"); SlowBonusMul = ((BaseUnityPlugin)this).Config.Bind("Rhythm", "SlowBonusMul", 1f, "Multiplier on BoostPerStroke for SLOW verdict (no penalty, no reward)"); TooFastPenaltyMul = ((BaseUnityPlugin)this).Config.Bind("Rhythm", "TooFastPenaltyMul", 0.3f, "Multiplier on BoostPerStroke for TOO FAST verdict (penalty)"); StreakMinForBonus = ((BaseUnityPlugin)this).Config.Bind("Streak", "StreakMinForBonus", 3, "Min streak count to start getting per-stroke streak bonus"); StreakBonusMul = ((BaseUnityPlugin)this).Config.Bind("Streak", "StreakBonusMul", 1.5f, "Extra multiplier on stroke boost when streak >= MinForBonus"); MasteryStreakMin = ((BaseUnityPlugin)this).Config.Bind("Streak", "MasteryStreakMin", 20, "Streak required to start applying mastery factor to ship speed"); MasteryStreakMax = ((BaseUnityPlugin)this).Config.Bind("Streak", "MasteryStreakMax", 50, "Streak at which mastery factor reaches maximum (plateau above)"); MasteryFactorAtMin = ((BaseUnityPlugin)this).Config.Bind("Streak", "MasteryFactorAtMin", 1.15f, "Ship speed multiplier when highest streak == MasteryStreakMin (1.0 = no bonus)"); MasteryFactorAtMax = ((BaseUnityPlugin)this).Config.Bind("Streak", "MasteryFactorAtMax", 1.3f, "Ship speed multiplier when highest streak >= MasteryStreakMax"); CrewSyncWindowRatio = ((BaseUnityPlugin)this).Config.Bind("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("CrewSync", "BonusFor2", 1.15f, "Speed multiplier when 2 rowers are in sync"); CrewSync3 = ((BaseUnityPlugin)this).Config.Bind("CrewSync", "BonusFor3", 1.3f, "Speed multiplier when 3 rowers are in sync"); CrewSync4Plus = ((BaseUnityPlugin)this).Config.Bind("CrewSync", "BonusFor4Plus", 1.45f, "Speed multiplier when 4+ rowers are in sync"); WrongSidePenaltyMul = ((BaseUnityPlugin)this).Config.Bind("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("Hud", "ShowHud", true, "Show the rowing HUD widget"); ShowMetronome = ((BaseUnityPlugin)this).Config.Bind("Hud", "ShowMetronome", true, "Show L/R indicator boxes (flash on player strokes)"); MetronomeSound = ((BaseUnityPlugin)this).Config.Bind("Hud", "MetronomeSound", true, "Play short tick sound on each metronome beat"); MetronomeVolume = ((BaseUnityPlugin)this).Config.Bind("Hud", "MetronomeVolume", 0.25f, "Volume of metronome tick (0.0-1.0)"); VerboseLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "VerboseLogs", false, "Log per-frame events (input, ticks). Spammy - turn off when not debugging."); InputModePref = ((BaseUnityPlugin)this).Config.Bind("Input", "Mode", RowingInput.InputMode.Auto, "Input mode"); LeftKey = ((BaseUnityPlugin)this).Config.Bind("Input", "LeftKey", (KeyCode)276, "Keyboard: left oar"); RightKey = ((BaseUnityPlugin)this).Config.Bind("Input", "RightKey", (KeyCode)275, "Keyboard: right oar"); LeftPadAxis = ((BaseUnityPlugin)this).Config.Bind("Input", "LeftPadAxis", "JoyAxis 9", "Pad: LT axis"); RightPadAxis = ((BaseUnityPlugin)this).Config.Bind("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(); 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 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(); 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("Arial.ttf"); _hudRoot = new GameObject("RowingTempoHud"); _hudRoot.transform.SetParent(((Component)hud).transform, false); Image val = _hudRoot.AddComponent(); ((Graphic)val).color = new Color(0f, 0f, 0f, 0.55f); RectTransform component = _hudRoot.GetComponent(); 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(); val3.font = builtinResource; val3.fontSize = 22; val3.fontStyle = (FontStyle)1; val3.alignment = (TextAnchor)3; val3.text = ""; Outline val4 = val2.AddComponent(); ((Shadow)val4).effectColor = new Color(0f, 0f, 0f, 0.85f); ((Shadow)val4).effectDistance = new Vector2(1.5f, -1.5f); RectTransform component2 = val2.GetComponent(); 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(); ((Graphic)val2).color = new Color(0.18f, 0.18f, 0.18f, 0.85f); RectTransform component = val.GetComponent(); 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(); 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(); 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(); 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(); 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(); 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(); } 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 GetCurrentShips() { if (CurrentShipsField == null) { return null; } return (List)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 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 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() != (Object)null)) { ShipOarsVisual shipOarsVisual = ((Component)__instance).gameObject.AddComponent(); 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("Rowing_UpdateSlot", (Method)delegate(long sender, int slot, float tempo, long playerId, double time) { OnUpdateSlot(__instance2, slot, tempo, playerId, time); }); nView.Register("Rowing_StrokeAt", (Method)delegate(long sender, int slot, long playerId, double atTime, int streak) { OnStrokeAt(__instance2, slot, playerId, atTime, streak); }); nView.Register("Rowing_ClaimSlot", (Action)delegate(long sender, int slot, long playerId, double time) { OnClaimSlot(__instance2, slot, playerId, time); }); nView.Register("Rowing_ReleaseSlot", (Action)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(); 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(); 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(); 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(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(); 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 _cachedSlots = new Dictionary(); private static readonly Dictionary _smoothedMultiplier = new Dictionary(); 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 list = new List(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 _cache = new Dictionary(); 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(); 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); } } } }