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 RowingRework v1.2.0
BepInEx/plugins/AllHandsOnboardRework/AllHandsOnboard.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; 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.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0")] [assembly: AssemblyProduct("AllHandsOnboard")] [assembly: AssemblyTitle("AllHandsOnboard")] [assembly: AssemblyVersion("1.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AllHandsOnboard { public static class AudioDefaults { public const float Volume = 0.65f; public static float MigrateVolume(float current) { if (!(Math.Abs(current - 0.25f) < 0.0001f)) { return current; } return 0.65f; } } public sealed class AudioRecoveryPolicy { private readonly Queue<double> _attempts = new Queue<double>(); private double _lastAttempt = -1000.0; public bool TryRepair(double now, bool playbackExpected, bool failed, bool globallyMutedOrPaused) { if (!playbackExpected || !failed || globallyMutedOrPaused) { return false; } while (_attempts.Count > 0 && now - _attempts.Peek() >= 60.0) { _attempts.Dequeue(); } if (now - _lastAttempt < 15.0 || _attempts.Count >= 3) { return false; } _attempts.Enqueue(now); _lastAttempt = now; return true; } } public static class BalrondCompatibility { public const string PluginId = "balrond.astafaraios.BalrondShipyard"; private static Type _managerType; private static FieldInfo _oarsField; public static bool Installed { get; private set; } public static bool Enabled { get { if (Installed && Plugin.EnableBalrond != null) { return Plugin.EnableBalrond.Value; } return false; } } public static void Initialize() { Installed = Chainloader.PluginInfos.TryGetValue("balrond.astafaraios.BalrondShipyard", out var value); if (Installed) { _managerType = ((object)value.Instance).GetType().Assembly.GetType("BalrondShipyard.ShipExtensionManager", throwOnError: false); _oarsField = _managerType?.GetField("oars", BindingFlags.Instance | BindingFlags.Public); Plugin.Log.LogInfo((object)("Optional Balrond Shipyard detected: " + value.Metadata.Version?.ToString() + "; rowing support=" + Enabled)); if (_oarsField == null) { Plugin.Log.LogWarning((object)"Shipyard oar renderer field not found; native-oar visibility integration unavailable for this version."); } } } public static GameObject GetNativeOars(Ship ship) { if (!Enabled || _managerType == null || _oarsField == null || (Object)(object)ship == (Object)null) { return null; } Component component = ((Component)ship).GetComponent(_managerType); if (!((Object)(object)component == (Object)null)) { object? value = _oarsField.GetValue(component); return (GameObject)((value is GameObject) ? value : null); } return null; } } internal sealed class NativeOarVisibility { private readonly Dictionary<Renderer, bool> _saved = new Dictionary<Renderer, bool>(); private float _nextScan; public void Update(Ship ship, bool hide) { if (!hide || !BalrondCompatibility.Enabled || !Plugin.HideBalrondOars.Value) { Restore(); return; } if (Time.unscaledTime >= _nextScan) { _nextScan = Time.unscaledTime + 0.5f; GameObject nativeOars = BalrondCompatibility.GetNativeOars(ship); if ((Object)(object)nativeOars != (Object)null) { Renderer[] componentsInChildren = nativeOars.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if (!_saved.ContainsKey(val)) { _saved.Add(val, val.enabled); } } } } foreach (KeyValuePair<Renderer, bool> item in _saved) { if ((Object)(object)item.Key != (Object)null) { item.Key.enabled = false; } } } public void Restore() { foreach (KeyValuePair<Renderer, bool> item in _saved) { if ((Object)(object)item.Key != (Object)null) { item.Key.enabled = item.Value; } } _saved.Clear(); _nextScan = 0f; } } public sealed class BalrondShipProfile { public string Label; public int Index; public int DefaultRowers; public float OarLength; public float Spread; } public static class BalrondShipCatalog { public static BalrondShipProfile Find(string objectName) { string text = (objectName ?? "").Trim().ToLowerInvariant().Replace("(clone)", "") .Trim(); switch (text) { case "knarr": case "knarrbal": return new BalrondShipProfile { Label = "Knarr", Index = 3, DefaultRowers = 2, OarLength = 3f, Spread = 0f }; case "snekke": case "snekkeship_bal": return new BalrondShipProfile { Label = "Snekke", Index = 4, DefaultRowers = 4, OarLength = 3.5f, Spread = 0.45f }; default: if (!text.StartsWith("dverger", StringComparison.Ordinal)) { return null; } goto case "holk"; case "holk": return new BalrondShipProfile { Label = "Holk", Index = 5, DefaultRowers = 6, OarLength = 4.2f, Spread = 0.6f }; } } } public sealed class BeatClock { private long _lastBeat = -1L; public void Reset() { _lastBeat = -1L; } public bool Advance(double seconds, double interval, out bool left) { long num = (long)Math.Floor(seconds / Math.Max(0.05, interval)); left = (num & 1) == 0; if (num == _lastBeat) { return false; } _lastBeat = num; return true; } } [BepInPlugin("com.allhands.onboard", "All hands onboard", "1.2.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private const string ModId = "com.allhands.onboard"; private const string ModName = "All hands onboard"; private const string ModVersion = "1.2.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<KeyCode> DrumTestKey; public static ConfigEntry<KeyCode> DrumResetKey; public static ConfigEntry<bool> AudioDiagnostics; public static ConfigEntry<bool> AudioAutoRecover; 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; public static ConfigEntry<float>[] OarWidth = new ConfigEntry<float>[6]; public static ConfigEntry<float>[] OarHeight = new ConfigEntry<float>[6]; public static ConfigEntry<float>[] OarForeAft = new ConfigEntry<float>[6]; public static ConfigEntry<bool> EnableBalrond; public static ConfigEntry<bool> HideBalrondOars; public static ConfigEntry<int>[] BalrondRowers = new ConfigEntry<int>[3]; private readonly Harmony _harmony = new Harmony("com.allhands.onboard"); private void BindOarSettings() { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown EnableBalrond = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility.BalrondShipyard", "Enabled", true, "Enable rowing on Knarr/Snekke/Holk when Balrond Shipyard is installed. Restart after changing."); HideBalrondOars = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility.BalrondShipyard", "HideNativeOarsWhileRowing", true, "Hide only Shipyard oar renderers while our rowing oars are visible. Upgrade state and rudder bonus are preserved."); string[] array = new string[6] { "Karve", "Longship", "Drakkar", "Knarr", "Snekke", "Holk" }; for (int i = 0; i < array.Length; i++) { string text = "Oars." + array[i]; if (i >= 3) { BalrondRowers[i - 3] = ((BaseUnityPlugin)this).Config.Bind<int>(text, "RowerCount", i switch { 4 => 4, 3 => 2, _ => 6, }, new ConfigDescription("Maximum rowing players/visible oars (0 disables). Match across clients and restart after changing.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 0, 2, 4, 6 }), Array.Empty<object>())); } OarWidth[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "WidthScale", OarTuning.DefaultWidth(i), new ConfigDescription("Mount spacing multiplier. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>())); OarHeight[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "HeightOffset", 0f, new ConfigDescription("Mount height adjustment in metres. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 2f), Array.Empty<object>())); OarForeAft[i] = ((BaseUnityPlugin)this).Config.Bind<float>(text, "ForeAftOffset", 0f, new ConfigDescription("Mount fore/aft adjustment in metres. Restart after changing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-3f, 3f), Array.Empty<object>())); } } private void Update() { MetronomeAudio.Tick(); } private void Awake() { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_055f: Unknown result type (might be due to invalid IL or missing references) //IL_0569: Expected O, but got Unknown //IL_0719: Unknown result type (might be due to invalid IL or missing references) //IL_0728: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"=== All hands onboard v1.2.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.5f, new ConfigDescription("Seconds between alternating strokes. 0.5 = 120 BPM; 0.6 = 100 BPM.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 1.5f), Array.Empty<object>())); ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "TimingMigration110", false, "One-time migration of the original 0.4-second default to 0.5 seconds."); if (!val.Value) { if (Mathf.Abs(TargetStrokeInterval.Value - 0.4f) < 0.0001f) { TargetStrokeInterval.Value = 0.5f; } val.Value = true; } BindOarSettings(); ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "MountCalibration112", false, "One-time calibration of untouched Longship/Drakkar width settings."); if (!val2.Value) { for (int i = 0; i < 3; i++) { OarWidth[i].Value = OarTuning.MigrateWidth(i, OarWidth[i].Value); } val2.Value = true; } 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 alternating timpani cues on each rowing beat"); MetronomeVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Hud", "MetronomeVolume", 0.65f, new ConfigDescription("Rowing timpani volume (0.0-1.0). Default 0.65.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); DrumTestKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Audio", "TestKey", (KeyCode)289, "Plays four timpani cues outside rowing at the default audition level. Respects game master volume. Set None to disable."); DrumResetKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Audio", "ResetKey", (KeyCode)290, "Rebuild mod audio and audition it. Ctrl+Shift+this key also resets the game audio engine. None disables the shortcut."); AudioDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "Diagnostics", true, "Log state changes, tests/recovery, and one playback status every 30 seconds while audio is active."); AudioAutoRecover = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "AutoRecover", true, "Repair this mod's failed audio source, at most three times per minute. Never automatically resets the game audio engine."); ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrade", "AudioLevel113", false, "One-time migration of the original 0.25 volume to 0.65. Custom levels and mute are preserved."); if (!val3.Value) { MetronomeVolume.Value = AudioDefaults.MigrateVolume(MetronomeVolume.Value); val3.Value = true; } 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}"); BalrondCompatibility.Initialize(); _harmony.PatchAll(); int num = 0; foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods()) { _ = patchedMethod; num++; } Log.LogInfo((object)$"Harmony PatchAll done - {num} method(s) patched"); MetronomeAudio.Initialize(); 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.FindObjectsByType<ShipOarsVisual>((FindObjectsSortMode)0); 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 { } MetronomeAudio.Clear(); ManualLogSource log5 = Log; if (log5 != null) { log5.LogInfo((object)"Mod cleanup complete"); } } } public static class DrumSynthesis { public const int SampleRate = 44100; public static float[] Create(bool accented) { int num = 81585; double[] array = new double[num]; Random random = new Random(accented ? 4109 : 8291); double num2 = (accented ? 65.406 : 97.999); double num3 = 0.0; double num4 = 0.0; double num5 = 0.0; double[] array2 = new double[6] { 1.0, 1.502, 2.008, 2.497, 3.018, 3.987 }; double[] array3 = new double[6] { 1.0, 0.56, 0.36, 0.2, 0.13, 0.065 }; for (int i = 0; i < num; i++) { double num6 = (double)i / 44100.0; num3 += Math.PI * 2.0 * num2 * (1.0 + 0.014 * Math.Exp((0.0 - num6) * 45.0)) / 44100.0; double num7 = 0.0; for (int j = 0; j < array2.Length; j++) { num7 += array3[j] * Math.Sin(num3 * array2[j]) * Math.Exp((0.0 - num6) * (4.8 + (double)j * 2.0)); } num4 += 0.4 * (random.NextDouble() * 2.0 - 1.0 - num4); num5 += 0.025 * (num4 - num5); double num8 = (num4 - num5) * (accented ? 0.85 : 0.95) * Math.Exp((0.0 - num6) * 75.0); double num9 = 1.0 - Math.Exp((0.0 - num6) * 650.0); array[i] = (num7 + num8) * num9; } int[] array4 = new int[6] { 1327, 1559, 1789, 1999, 2137, 2381 }; double[] array5 = new double[num]; int[] array6 = array4; foreach (int num10 in array6) { double[] array7 = new double[num10]; double num11 = Math.Pow(10.0, -3.0 * (double)num10 / 59535.00000000001); double num12 = 0.0; for (int l = 0; l < num; l++) { int num13 = l % num10; double num14 = array7[num13]; num12 += 0.23 * (num14 - num12); array7[num13] = ((l >= 1103) ? array[l - 1103] : 0.0) + num12 * num11; array5[l] += num14 / (double)array4.Length; } } array6 = new int[2] { 223, 73 }; foreach (int num15 in array6) { double[] array8 = new double[num15]; for (int m = 0; m < num; m++) { int num16 = m % num15; double num17 = array5[m]; array5[m] = array8[num16] - 0.55 * num17; array8[num16] = num17 + 0.55 * array5[m]; } } float[] array9 = new float[num]; double num18 = 0.0; double num19 = 0.0; for (int n = 0; n < num; n++) { double num20 = array[n] * 0.95 + array5[n] * 0.46; double num21 = num20 - num18 + 0.998 * num19; num18 = num20; num19 = num21; double num22 = Math.Min(1.0, (double)(num - 1 - n) / 9702.0); array9[n] = (float)(num21 * num22); } float val = 0f; float[] array10 = array9; foreach (float value in array10) { val = Math.Max(val, Math.Abs(value)); } float num23 = (accented ? 0.72f : 0.68f) / Math.Max(val, 0.001f); for (int num24 = 0; num24 < num; num24++) { array9[num24] *= num23; } return array9; } } [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 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) 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.FindObjectsByType<Ship>((FindObjectsSortMode)0); 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: 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_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: 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); ((Graphic)_hudRoot.AddComponent<Image>()).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 val = new GameObject("Verdict"); val.transform.SetParent(_hudRoot.transform, false); Text val2 = val.AddComponent<Text>(); val2.font = builtinResource; val2.fontSize = 22; val2.fontStyle = (FontStyle)1; val2.alignment = (TextAnchor)3; val2.text = ""; Outline obj = val.AddComponent<Outline>(); ((Shadow)obj).effectColor = new Color(0f, 0f, 0f, 0.85f); ((Shadow)obj).effectDistance = new Vector2(1.5f, -1.5f); RectTransform component2 = val.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 = val2; } 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Image obj = val.AddComponent<Image>(); ((Graphic)obj).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 val2 = new GameObject("Label"); val2.transform.SetParent(val.transform, false); Text obj2 = val2.AddComponent<Text>(); obj2.font = font; obj2.fontSize = 14; obj2.fontStyle = (FontStyle)1; obj2.alignment = (TextAnchor)4; ((Graphic)obj2).color = Color.white; obj2.text = label; RectTransform component2 = val2.GetComponent<RectTransform>(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; return obj; } 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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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 { [CompilerGenerated] private static class <>O { public static AudioConfigurationChangeHandler <0>__OnAudioConfigurationChanged; } private static AudioClip _left; private static AudioClip _right; private static AudioSource _source; private static readonly BeatClock Clock = new BeatClock(); private static bool _active; private static int _testBeats; private static float _testNext; private static float _testEnd; private static float _diagnosticAt = -1f; private static bool _loggedDisabled; private static readonly AudioRecoveryPolicy Recovery = new AudioRecoveryPolicy(); private static bool _subscribed; private static volatile bool _deviceChanged; private static float _lastCueAt = -100f; private static float _nextHealthCheck; private static float _nextStatus; private static float _lastError = -100f; private static double _lastDsp = -1.0; private static long _cueCount; private static bool _lastLeft; private static float _lastVolume; private static string _lastHealth = ""; public static void Initialize() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (!_subscribed) { object obj = <>O.<0>__OnAudioConfigurationChanged; if (obj == null) { AudioConfigurationChangeHandler val = OnAudioConfigurationChanged; <>O.<0>__OnAudioConfigurationChanged = val; obj = (object)val; } AudioSettings.OnAudioConfigurationChanged += (AudioConfigurationChangeHandler)obj; _subscribed = true; } } private static void OnAudioConfigurationChanged(bool deviceWasChanged) { _deviceChanged = true; Plugin.Log.LogInfo((object)("Timpani audio configuration changed; deviceChanged=" + deviceWasChanged)); } private static void Rebuild(string reason) { Plugin.Log.LogWarning((object)("Timpani source rebuild: " + reason)); if ((Object)(object)_source != (Object)null) { _source.Stop(); Object.Destroy((Object)(object)((Component)_source).gameObject); } if ((Object)(object)_left != (Object)null) { Object.Destroy((Object)(object)_left); } if ((Object)(object)_right != (Object)null) { Object.Destroy((Object)(object)_right); } _source = null; _left = (_right = null); _lastCueAt = -100f; Clock.Reset(); } private static void RecoverManually(bool resetEngine) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) LogAudioState("before manual recovery"); if (resetEngine) { bool flag = AudioSettings.Reset(AudioSettings.GetConfiguration()); Plugin.Log.LogWarning((object)("Manual game audio engine reset returned " + flag)); } Rebuild(resetEngine ? "manual game audio reset" : "manual mod-only reset"); _deviceChanged = false; BeginTest(); } private static void CheckHealth() { float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextHealthCheck) { return; } _nextHealthCheck = unscaledTime + 1f; bool flag = AudioListener.pause || Time.timeScale == 0f; bool flag2 = AudioListener.volume <= 0f; int num = Object.FindObjectsByType<AudioListener>((FindObjectsSortMode)0).Count((AudioListener listener) => ((Behaviour)listener).isActiveAndEnabled); bool flag3 = (_active || unscaledTime < _testEnd) && unscaledTime - _lastCueAt >= 0.1f && unscaledTime - _lastCueAt < 1.5f; bool flag4 = (Object)(object)_source == (Object)null || !((Behaviour)_source).isActiveAndEnabled || _source.mute || _source.volume <= 0f || !_source.isPlaying; bool flag5 = flag3 && !flag && !flag2 && num > 0 && _lastDsp >= 0.0 && AudioSettings.dspTime == _lastDsp; string text = (flag ? "paused" : (flag2 ? "master muted" : ((num == 0) ? "no active listener" : (flag5 ? "DSP clock stalled" : ((flag3 && flag4) ? "playback expected but source not playing/enabled" : "ready"))))); if (text != _lastHealth) { _lastHealth = text; if (Plugin.AudioDiagnostics.Value) { LogAudioState("state=" + text); } if (flag5) { Plugin.Log.LogWarning((object)"Audio DSP clock appears stalled. Try Ctrl+Shift+the audio reset key (default F9)."); } } _lastDsp = AudioSettings.dspTime; if (Plugin.AudioAutoRecover.Value && Recovery.TryRepair(unscaledTime, flag3, flag4, flag || flag2 || num == 0)) { bool lastLeft = _lastLeft; float lastVolume = _lastVolume; Rebuild("watchdog detected failed playback"); PlayCue(lastLeft, lastVolume); _diagnosticAt = unscaledTime + 0.12f; } if (unscaledTime >= _nextStatus && Plugin.AudioDiagnostics.Value) { _nextStatus = unscaledTime + 30f; LogAudioState("periodic status"); } } private static void BeginTest() { if ((Object)(object)_source != (Object)null) { _source.Stop(); } _active = false; _lastCueAt = -100f; _testBeats = 4; _testNext = Time.unscaledTime; _testEnd = Time.unscaledTime + 3.5f; Clock.Reset(); Plugin.Log.LogInfo((object)"Timpani sound test requested: four alternating hits at volume 0.65, independent of rowing and cue mute."); LogAudioState("test start"); } private static void LogAudioState(string context) { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) int num = Object.FindObjectsByType<AudioListener>((FindObjectsSortMode)0).Count((AudioListener listener) => ((Behaviour)listener).isActiveAndEnabled); Plugin.Log.LogInfo((object)("Timpani " + context + ": listeners=" + num + ", master=" + AudioListener.volume + ", paused=" + AudioListener.pause + ", sourceEnabled=" + ((Object)(object)_source != (Object)null && ((Behaviour)_source).isActiveAndEnabled) + ", playing=" + ((Object)(object)_source != (Object)null && _source.isPlaying) + ", sourceMute=" + ((Object)(object)_source != (Object)null && _source.mute) + ", clipState=" + (((Object)(object)_left != (Object)null) ? ((object)_left.loadState/*cast due to .constrained prefix*/).ToString() : "not created") + ", sampleRate=" + AudioSettings.outputSampleRate + ", dspTime=" + AudioSettings.dspTime + ", submittedCues=" + _cueCount + ", cueVolume=" + _lastVolume)); } public static AudioClip GetClip(bool left) { AudioClip val = (left ? _left : _right); if ((Object)(object)val != (Object)null) { return val; } float[] array = DrumSynthesis.Create(left); AudioClip val2 = AudioClip.Create(left ? "TimpaniLowC" : "TimpaniHighG", array.Length, 1, 44100, false); if (!val2.SetData(array, 0)) { Object.Destroy((Object)(object)val2); throw new InvalidOperationException("Could not populate timpani audio clip."); } if (left) { _left = val2; } else { _right = val2; } return val2; } public static void Tick() { try { TickCore(); } catch (Exception ex) { if (Time.unscaledTime - _lastError > 10f) { _lastError = Time.unscaledTime; Plugin.Log.LogError((object)("Timpani update failed; use the reset key (default F9). " + ex)); } } } private static void TickCore() { //IL_0027: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Invalid comparison between Unknown and I4 if (_deviceChanged) { _deviceChanged = false; Rebuild("audio device/configuration changed"); } if (Plugin.DrumResetKey != null && (int)Plugin.DrumResetKey.Value != 0 && Input.GetKeyDown(Plugin.DrumResetKey.Value)) { bool num = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); bool flag = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); RecoverManually(num && flag); } if (Plugin.DrumTestKey != null && (int)Plugin.DrumTestKey.Value != 0 && Input.GetKeyDown(Plugin.DrumTestKey.Value)) { BeginTest(); } if (_active || Time.unscaledTime < _testEnd) { CheckHealth(); } if (_diagnosticAt >= 0f && Time.unscaledTime >= _diagnosticAt) { _diagnosticAt = -1f; LogAudioState("after PlayOneShot"); } if (Time.unscaledTime < _testEnd) { if (_testBeats > 0 && Time.unscaledTime >= _testNext) { PlayCue(_testBeats % 2 == 0, 0.65f); if (_testBeats == 4) { _diagnosticAt = Time.unscaledTime + 0.12f; } _testBeats--; _testNext = Time.unscaledTime + 0.5f; } return; } if (Plugin.MetronomeSound == null || (Object)(object)Player.m_localPlayer == (Object)null) { Stop(); return; } Ship val = Player.m_localPlayer.GetControlledShip(); if ((Object)(object)val == (Object)null) { val = Ship.GetLocalShip(); } if (!((Object)(object)val != (Object)null) || (int)val.GetSpeedSetting() != 2 || ShipTypeConfig.GetFor(val).RowerCount <= 0) { _loggedDisabled = false; Stop(); } else if (!Plugin.MetronomeSound.Value || Plugin.MetronomeVolume.Value <= 0f) { if (!_loggedDisabled) { Plugin.Log.LogInfo((object)("Timpani cues disabled by config: MetronomeSound=" + Plugin.MetronomeSound.Value + ", volume=" + Plugin.MetronomeVolume.Value + ". F8 auditions the sound independently.")); _loggedDisabled = true; } Stop(); } else if (Time.timeScale == 0f || AudioListener.pause) { Clock.Reset(); } else { if (!_active) { _active = true; Plugin.Log.LogInfo((object)("Rowing timpani active: 2D playback; volume=" + Plugin.MetronomeVolume.Value)); _diagnosticAt = Time.unscaledTime + 0.12f; } double seconds = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : ((double)Time.time)); if (Clock.Advance(seconds, Plugin.TargetStrokeInterval.Value, out var left)) { PlayTick(left); } } } public static void PlayTick(bool left) { if (Plugin.MetronomeSound.Value) { PlayCue(left, Mathf.Clamp01(Plugin.MetronomeVolume.Value)); } } private static void PlayCue(bool left, float volume) { //IL_0028: 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_0033: Expected O, but got Unknown _lastCueAt = Time.unscaledTime; _lastLeft = left; _lastVolume = volume; if ((Object)(object)_source == (Object)null) { GameObject val = new GameObject("AllHandsOnboard_Drums"); Object.DontDestroyOnLoad((Object)val); _source = val.AddComponent<AudioSource>(); _source.playOnAwake = false; _source.loop = false; _source.spatialBlend = 0f; _source.dopplerLevel = 0f; _source.volume = 1f; _source.priority = 32; _source.bypassReverbZones = true; } _source.PlayOneShot(GetClip(left), volume); _cueCount++; } private static void Stop() { if (_active && (Object)(object)_source != (Object)null) { _source.Stop(); } _active = false; Clock.Reset(); } public static void Clear() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if (_subscribed) { object obj = <>O.<0>__OnAudioConfigurationChanged; if (obj == null) { AudioConfigurationChangeHandler val = OnAudioConfigurationChanged; <>O.<0>__OnAudioConfigurationChanged = val; obj = (object)val; } AudioSettings.OnAudioConfigurationChanged -= (AudioConfigurationChangeHandler)obj; } _subscribed = false; _deviceChanged = false; Stop(); _testBeats = 0; _testEnd = 0f; _diagnosticAt = -1f; if ((Object)(object)_source != (Object)null) { Object.Destroy((Object)(object)((Component)_source).gameObject); } if ((Object)(object)_left != (Object)null) { Object.Destroy((Object)(object)_left); } if ((Object)(object)_right != (Object)null) { Object.Destroy((Object)(object)_right); } _source = null; _left = (_right = null); } } public sealed class OarMeshData { public float[] Positions; public float[] UV; public int[] Triangles; } public static class OarMeshGeometry { public static OarMeshData Create(float outboardLength) { float[] obj = new float[11] { 0.85f, 0.78f, 0.3f, 0.08f, -0.08f, -0.18f, 0f, 0f, 0f, 0f, 0f }; obj[6] = (0f - outboardLength) * 0.64f; obj[7] = (0f - outboardLength) * 0.74f; obj[8] = (0f - outboardLength) * 0.81f; obj[9] = (0f - outboardLength) * 0.94f; obj[10] = 0f - outboardLength; float[] array = obj; float[] array2 = new float[11] { 0.027f, 0.038f, 0.051f, 0.061f, 0.061f, 0.05f, 0.043f, 0.065f, 0.155f, 0.145f, 0.09f }; float[] array3 = new float[11] { 0.027f, 0.038f, 0.051f, 0.061f, 0.061f, 0.05f, 0.039f, 0.023f, 0.018f, 0.017f, 0.012f }; List<float> list = new List<float>(); List<float> list2 = new List<float>(); List<int> list3 = new List<int>(); for (int i = 0; i < array.Length; i++) { for (int j = 0; j < 8; j++) { double num = Math.PI * 2.0 * (double)j / 8.0; list.Add((float)Math.Cos(num) * array2[i]); list.Add(array[i]); list.Add((float)Math.Sin(num) * array3[i]); list2.Add((float)j / 8f); list2.Add((array[i] + outboardLength) / (outboardLength + 0.85f)); } } for (int k = 0; k < array.Length - 1; k++) { for (int l = 0; l < 8; l++) { int num2 = k * 8 + l; int num3 = k * 8 + (l + 1) % 8; int num4 = num2 + 8; int num5 = num3 + 8; list3.AddRange(new int[6] { num2, num3, num4, num3, num5, num4 }); } } for (int m = 0; m < 2; m++) { int num6 = ((m != 0) ? (array.Length - 1) : 0); int num7 = list.Count / 3; list.Add(0f); list.Add(array[num6]); list.Add(0f); list2.Add(0.5f); list2.Add((m == 0) ? 1 : 0); for (int n = 0; n < 8; n++) { int num8 = num6 * 8 + n; int num9 = num6 * 8 + (n + 1) % 8; list3.AddRange((m == 0) ? new int[3] { num7, num9, num8 } : new int[3] { num7, num8, num9 }); } } return new OarMeshData { Positions = list.ToArray(), UV = list2.ToArray(), Triangles = list3.ToArray() }; } } public static class OarPose { public static void Direction(bool left, double cycle, bool rowing, out float x, out float y, out float z) { double num = (rowing ? (Math.Sin(cycle * 2.0 * Math.PI) * 28.0 * Math.PI / 180.0) : 0.0); double num2 = (rowing ? (8.0 + 18.0 * Math.Cos(cycle * 2.0 * Math.PI)) : 6.0) * Math.PI / 180.0; x = (float)((double)((!left) ? 1 : (-1)) * Math.Cos(num2) * Math.Cos(num)); y = (float)(0.0 - Math.Sin(num2)); z = (float)(Math.Cos(num2) * Math.Sin(num)); } } public static class OarTuning { public static float DefaultWidth(int type) { return type switch { 2 => 1.3f, 1 => 0.75f, _ => 1f, }; } public static float MigrateWidth(int type, float current) { if (!(Math.Abs(current - 1f) < 0.0001f)) { return current; } return DefaultWidth(type); } } 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 != InputMode.Auto) { 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 num2 = (_leftTriggerPrev ? (num > 0.3f) : (num > 0.5f)); if (num2 && !_leftTriggerPrev) { _bufferedLeft = true; } _leftTriggerPrev = num2; float num3 = SafeGetAxis(Plugin.RightPadAxis.Value); bool num4 = (_rightTriggerPrev ? (num3 > 0.3f) : (num3 > 0.5f)); if (num4 && !_rightTriggerPrev) { _bufferedRight = true; } _rightTriggerPrev = num4; } 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) { return ((object)(isLeft ? Plugin.LeftKey.Value : Plugin.RightKey.Value)/*cast due to .constrained prefix*/).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 BackwardForceField = AccessTools.Field(typeof(Ship), "m_backwardForce"); private static readonly FieldInfo PlayerAttachPointField = AccessTools.Field(typeof(Player), "m_attachPoint"); public static float GetBackwardForce(Ship ship) { if (!((Object)(object)ship == (Object)null) && !(BackwardForceField == null)) { return (float)BackwardForceField.GetValue(ship); } return 0f; } 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)) { ((Component)__instance).gameObject.AddComponent<ShipOarsVisual>().Init(__instance); } } } [HarmonyPatch(typeof(Ship), "Awake")] public static class ShipAwakeRpcPatch { private static void Postfix(Ship __instance) { if ((Object)(object)__instance == (Object)null) { return; } ZNetView nView = ShipAccess.GetNView(__instance); if ((Object)(object)nView == (Object)null) { Plugin.Log.LogWarning((object)("[ShipAwakeRpc] Ship '" + ((Object)((Component)__instance).gameObject).name + "' has NO ZNetView - RPC not registered!")); return; } Plugin.Log.LogDebug((object)$"[ShipAwakeRpc] Registering RPCs on '{((Object)((Component)__instance).gameObject).name}' (instance {((Object)__instance).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(__instance, 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(__instance, 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(__instance, slot, playerId, time); }); nView.Register<int, long>("Rowing_ReleaseSlot", (Action<long, int, long>)delegate(long sender, int slot, long playerId) { OnReleaseSlot(__instance, 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(); if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == 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(); if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == 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 num = zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L); float num2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[slot], 0f); if (num == 0L || num == playerId || timeSeconds - (double)num2 > (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 {num}, age={timeSeconds - (double)num2: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(); if (zDO.GetLong(RowingZdoKeys.SlotPlayer[slot], 0L) == 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 { private static void Prefix(Ship __instance, float fixedDeltaTime) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 bool flag = (int)__instance.GetSpeedSetting() == 2; if (flag && ShipTypeConfig.GetFor(__instance).RowerCount > 0) { ShipRowingManager.TickSmoothing(__instance, fixedDeltaTime); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return; } bool flag2 = __instance.IsPlayerInBoat(localPlayer) && ShipAccess.IsPlayerSeatedOnShip(localPlayer, __instance); if (!flag || !flag2) { if (LocalRowingState.CurrentSlot != -1 && (Object)(object)LocalRowingState.CurrentShip == (Object)(object)__instance) { ShipRowingManager.ReleaseSlot(__instance, LocalRowingState.CurrentSlot, localPlayer.GetPlayerID()); LocalRowingState.SetSlot(-1, null); } } else { if (LocalRowingState.CurrentSlot == -1) { LocalRowingState.SetSlot(ShipRowingManager.ClaimSlot(__instance, localPlayer.GetPlayerID()), __instance); } LocalRowingState.Tick(fixedDeltaTime, __instance); } } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { FieldInfo drive = typeof(Ship).GetField("m_backwardForce", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo replacement = typeof(ShipFixedUpdatePatch).GetMethod("GetDriveForce", BindingFlags.Static | BindingFlags.NonPublic); int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, drive)) { instruction.opcode = OpCodes.Call; instruction.operand = replacement; replaced++; } yield return instruction; } if (replaced == 0) { Plugin.Log.LogWarning((object)"No paddle-force reads found: rowing boost disabled; vanilla flotation is unchanged."); } } internal static float GetDriveForce(Ship ship) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 float backwardForce = ShipAccess.GetBackwardForce(ship); if ((int)ship.GetSpeedSetting() != 2 || ShipTypeConfig.GetFor(ship).RowerCount == 0) { return backwardForce; } return backwardForce * ShipRowingManager.GetSmoothedMultiplier(ship); } } public class ShipOarsVisual : MonoBehaviour { private Ship _ship; private readonly NativeOarVisibility _nativeOars = new NativeOarVisibility(); private ShipTypeConfig.TypeData _typeData; private GameObject[] _roots; private double[] _lastStroke; private bool _built; private Mesh _mesh; private Material _material; private Texture2D _grain; public void Init(Ship ship) { _ship = ship; } private void TryBuild() { //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0151: 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_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Expected O, but got Unknown //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Expected O, but got Unknown //IL_0374: Unknown result type (might be due to invalid IL or missing references) if (_built || (Object)(object)_ship == (Object)null) { return; } _typeData = ShipTypeConfig.GetFor(_ship); _built = true; if (_typeData.RowerCount == 0) { return; } Material val = null; MeshRenderer[] componentsInChildren = ((Component)_ship).GetComponentsInChildren<MeshRenderer>(true); foreach (MeshRenderer val2 in componentsInChildren) { Material sharedMaterial = ((Renderer)val2).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || (Object)(object)sharedMaterial.shader == (Object)null) { continue; } string name = ((Object)sharedMaterial.shader).name; if (!name.Contains("Hidden") && !name.Contains("Particle") && !(name == "Standard") && sharedMaterial.HasProperty("_MainTex")) { val = sharedMaterial; if (((Object)val2).name.ToLowerInvariant().Contains("hull")) { break; } } } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"No compatible ship material found; oar visuals disabled for this ship."); return; } _material = new Material(val); ((Object)_material).name = "RowingOar_OiledWood"; _grain = MakeWoodGrain(); _material.SetTexture("_MainTex", (Texture)(object)_grain); _material.SetTextureScale("_MainTex", Vector2.one); _material.SetTextureOffset("_MainTex", Vector2.zero); if (_material.HasProperty("_Color")) { _material.SetColor("_Color", Color.white); } if (_material.HasProperty("_BumpMap")) { _material.SetTexture("_BumpMap", (Texture)null); } if (_material.HasProperty("_MetallicGlossMap")) { _material.SetTexture("_MetallicGlossMap", (Texture)null); } if (_material.HasProperty("_Metallic")) { _material.SetFloat("_Metallic", 0f); } if (_material.HasProperty("_Glossiness")) { _material.SetFloat("_Glossiness", 0.15f); } OarMeshData oarMeshData = OarMeshGeometry.Create(_typeData.OarLength); Vector3[] array = (Vector3[])(object)new Vector3[oarMeshData.Positions.Length / 3]; Vector2[] array2 = (Vector2[])(object)new Vector2[array.Length]; for (int j = 0; j < array.Length; j++) { array[j] = new Vector3(oarMeshData.Positions[j * 3], oarMeshData.Positions[j * 3 + 1], oarMeshData.Positions[j * 3 + 2]); array2[j] = new Vector2(oarMeshData.UV[j * 2], oarMeshData.UV[j * 2 + 1]); } _mesh = new Mesh { name = "VikingRowingOar", vertices = array, uv = array2, triangles = oarMeshData.Triangles }; _mesh.RecalculateNormals(); _mesh.RecalculateBounds(); _roots = (GameObject[])(object)new GameObject[_typeData.RowerCount]; _lastStroke = new double[_roots.Length]; for (int k = 0; k < _roots.Length; k++) { GameObject val3 = new GameObject("RowingOar_" + k); val3.transform.SetParent(((Component)this).transform, false); val3.transform.localPosition = _typeData.MountPoints[k]; val3.AddComponent<MeshFilter>().sharedMesh = _mesh; ((Renderer)val3.AddComponent<MeshRenderer>()).sharedMaterial = _material; _roots[k] = val3; Pose(val3.transform, k, 0.0, rowing: false); } } private static Texture2D MakeWoodGrain() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_00a6: 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) Texture2D val = new Texture2D(64, 256, (TextureFormat)3, false); ((Object)val).name = "OarWoodGrain"; ((Texture)val).wrapMode = (TextureWrapMode)0; Color[] array = (Color[])(object)new Color[16384]; for (int i = 0; i < 256; i++) { for (int j = 0; j < 64; j++) { float num = Mathf.Sin((float)j * 1.7f + Mathf.Sin((float)i * 0.037f) * 0.6f); float num2 = 0.9f + 0.1f * num + 0.035f * Mathf.Sin((float)j * 4.3f + (float)i * 0.12f); array[i * 64 + j] = new Color(0.53f * num2, 0.32f * num2, 0.15f * num2); } } val.SetPixels(array); val.Apply(false, true); return val; } private static void Pose(Transform root, int slot, double cycle, bool rowing, bool smooth = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //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_002f: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) OarPose.Direction(slot % 2 == 0, cycle, rowing, out var x, out var y, out var z); Quaternion val = Quaternion.FromToRotation(Vector3.down, new Vector3(x, y, z)); root.localRotation = (smooth ? Quaternion.Slerp(root.localRotation, val, 1f - Mathf.Exp(-18f * Time.deltaTime)) : val); } private void Update() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_ship == (Object)null) { return; } if (!_built) { TryBuild(); } if (_roots == null) { _nativeOars.Restore(); return; } bool flag = (int)_ship.GetSpeedSetting() == 2; _nativeOars.Update(_ship, flag); ShipRowingManager.SlotData[] array = (flag ? ShipRowingManager.GetSlots(_ship) : null); double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : ((double)Time.time)); double num2 = Plugin.TargetStrokeInterval.Value; for (int i = 0; i < _roots.Length; i++) { GameObject val = _roots[i]; val.SetActive(flag); if (flag) { ShipRowingManager.SlotData slotData = array[i]; double num3 = num - slotData.LastStrokeAt; bool flag2 = slotData.PlayerId != 0L && slotData.LastStrokeAt > 0.0 && num3 >= 0.0 && num3 < num2; Pose(val.transform, i, flag2 ? (num3 / num2) : 0.0, flag2, smooth: true); if (flag2 && slotData.LastStrokeAt > _lastStroke[i]) { _lastStroke[i] = slotData.LastStrokeAt; SplashAudio.PlayAt(val.transform.TransformPoint(Vector3.down * _typeData.OarLength), 0.2f); } } } } private void OnDisable() { _nativeOars.Restore(); } private void OnDestroy() { _nativeOars.Restore(); if (_roots != null) { GameObject[] roots = _roots; foreach (GameObject val in roots) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } if ((Object)(object)_mesh != (Object)null) { Object.Destroy((Object)(object)_mesh); } if ((Object)(object)_material != (Object)null) { Object.Destroy((Object)(object)_material); } if ((Object)(object)_grain != (Object)null) { Object.Destroy((Object)(object)_grain); } } } 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 var 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 num2 = zDO.GetLong(RowingZdoKeys.SlotPlayer[i], 0L); float tempo = zDO.GetFloat(RowingZdoKeys.SlotTempo[i], 0f); float num3 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[i], 0f); float num4 = zDO.GetFloat(RowingZdoKeys.SlotLastStroke[i], 0f); int streak = zDO.GetInt(RowingZdoKeys.SlotStreak[i], 0); if (num2 == 0L || num - (double)num3 > (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 = num2; value[i].Tempo = tempo; value[i].LastUpdate = num3; value[i].LastStrokeAt = num4; value[i].Streak = streak; } } 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; return Mathf.Min(num, num2) * 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 > (double)Plugin.TargetStrokeInterval.Value * 2.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 num = zDO.GetLong(RowingZdoKeys.SlotPlayer[j], 0L); float num2 = zDO.GetFloat(RowingZdoKeys.SlotLastUpdate[j], 0f); if (num == 0L || timeSeconds - (double)num2 > (double)value) { Plugin.Log.LogDebug((object)$"[ClaimSlot] Player {playerId} claiming slot {j} on '{((Object)((Component)ship).gameObject).name}' (was pid={num}, age={timeSeconds - (double)num2: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 var 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_00d1: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0105: 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) string text = (((Object)((Component)ship).gameObject).name ?? "").ToLowerInvariant(); Bounds hull = EstimateHullBounds(ship); BalrondShipProfile balrondShipProfile = BalrondShipCatalog.Find(text); if (balrondShipProfile != null) { if (!BalrondCompatibility.Enabled) { return None(balrondShipProfile.Label); } int value = Plugin.BalrondRowers[balrondShipProfile.Index - 3].Value; if (value == 0) { return None(balrondShipProfile.Label); } return MakeData(balrondShipProfile.Label, value, hull, balrondShipProfile.OarLength, (value <= 2) ? 0f : ((balrondShipProfile.Spread > 0f) ? balrondShipProfile.Spread : 0.4f), balrondShipProfile.Index); } 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"