using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BreakAnkles;
using HarmonyLib;
using Il2CppFemur;
using Il2CppGB.Game;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Users;
using UnityEngine.InputSystem.Utilities;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "Break Ankles", "1.0.0", "BigPickle", null)]
[assembly: MelonGame("Boneloaf", "Gang Beasts")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("BigPickle")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Backdash with Whoosh + Meme SFX. Press Right Shift / RT to backdash: 3s cooldown, attacks landing during the dash deal 0 damage. Local play only.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BreakAnkles")]
[assembly: AssemblyTitle("BreakAnkles")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace BreakAnkles
{
public static class Config
{
private static MelonPreferences_Category _cat;
public static MelonPreferences_Entry<bool> Enabled;
public static MelonPreferences_Entry<bool> RequireIncomingAttack;
public static MelonPreferences_Entry<bool> LocalOnly;
public static MelonPreferences_Entry<float> CooldownSeconds;
public static MelonPreferences_Entry<float> DashSpeed;
public static MelonPreferences_Entry<float> HopSpeed;
public static MelonPreferences_Entry<float> IFrameSeconds;
public static MelonPreferences_Entry<float> AttackRange;
public static MelonPreferences_Entry<float> Volume;
public static MelonPreferences_Entry<string> KeyboardButton;
public static MelonPreferences_Entry<string> GamepadButton;
public static void Init()
{
_cat = MelonPreferences.CreateCategory("BreakAnkles", "Break Ankles");
Enabled = _cat.CreateEntry<bool>("Enabled", true, "Enabled", (string)null, false, false, (ValueValidator)null, (string)null);
RequireIncomingAttack = _cat.CreateEntry<bool>("RequireIncomingAttack", false, "Only dodge when an attack is incoming", (string)null, false, false, (ValueValidator)null, (string)null);
LocalOnly = _cat.CreateEntry<bool>("LocalOnly", true, "Only work in local play (disabled online)", (string)null, false, false, (ValueValidator)null, (string)null);
CooldownSeconds = _cat.CreateEntry<float>("CooldownSeconds", 3f, "Cooldown between dodges (seconds)", (string)null, false, false, (ValueValidator)null, (string)null);
DashSpeed = _cat.CreateEntry<float>("DashSpeed", 8f, "Backdash strength (velocity)", (string)null, false, false, (ValueValidator)null, (string)null);
HopSpeed = _cat.CreateEntry<float>("HopSpeed", 3f, "Hop height (upward velocity)", (string)null, false, false, (ValueValidator)null, (string)null);
IFrameSeconds = _cat.CreateEntry<float>("IFrameSeconds", 0.8f, "0-damage window during a backdash (seconds)", (string)null, false, false, (ValueValidator)null, (string)null);
AttackRange = _cat.CreateEntry<float>("AttackRange", 3.2f, "Attack detection range (meters)", (string)null, false, false, (ValueValidator)null, (string)null);
Volume = _cat.CreateEntry<float>("Volume", 1f, "Sound effect volume", (string)null, false, false, (ValueValidator)null, (string)null);
KeyboardButton = _cat.CreateEntry<string>("KeyboardButton", "rightShift", "Keyboard button (Unity Input System path)", (string)null, false, false, (ValueValidator)null, (string)null);
GamepadButton = _cat.CreateEntry<string>("GamepadButton", "rightTrigger", "Gamepad button (Unity Input System path)", (string)null, false, false, (ValueValidator)null, (string)null);
MelonPreferences.Save();
}
}
public static class Dodge
{
private sealed class DodgeState
{
public float CooldownUntil = float.MinValue;
public float InvulnUntil = float.MinValue;
public bool WeSetInvuln;
public bool LastInputDown;
}
private static readonly List<Actor> LocalList = new List<Actor>();
private static readonly List<DodgeState> StateList = new List<DodgeState>();
public static bool IsInvulnerable(Actor actor)
{
if ((Object)(object)actor == (Object)null || !actor.IsLocal)
{
return false;
}
DodgeState dodgeState = FindState(actor);
if (dodgeState != null)
{
return Time.time < dodgeState.InvulnUntil;
}
return false;
}
public static void Update()
{
if (!Config.Enabled.Value || (Config.LocalOnly.Value && !IsLocalGame()))
{
return;
}
List<Actor> cachedActors = Actor.CachedActors;
if (cachedActors == null || cachedActors.Count == 0)
{
return;
}
for (int i = 0; i < cachedActors.Count; i++)
{
Actor val = cachedActors[i];
if (!((Object)(object)val == (Object)null) && val.IsLocal && !((Object)(object)val.statusHandeler == (Object)null))
{
ProcessLocal(val);
}
}
}
private static void ProcessLocal(Actor local)
{
DodgeState state = GetState(local);
if (state.WeSetInvuln && Time.time >= state.InvulnUntil)
{
state.WeSetInvuln = false;
local.statusHandeler.invulnerable = false;
}
if (Time.time < state.CooldownUntil)
{
return;
}
bool flag = ReadDodgeInput(local);
bool num = flag && !state.LastInputDown;
state.LastInputDown = flag;
if (num)
{
Actor val = FindAttacker(local);
if (!Config.RequireIncomingAttack.Value || !((Object)(object)val == (Object)null))
{
DoDodge(local, val);
}
}
}
private static void DoDodge(Actor local, Actor attacker)
{
DodgeState state = GetState(local);
state.CooldownUntil = Time.time + Config.CooldownSeconds.Value;
state.InvulnUntil = Time.time + Config.IFrameSeconds.Value;
if (!local.statusHandeler.invulnerable)
{
local.statusHandeler.invulnerable = true;
state.WeSetInvuln = true;
}
ApplyDash(local, attacker);
Mod.Log.Msg("BACKDASH! (" + ((Object)local).name + ")");
SoundBank.PlayWeave();
SoundBank.PlayBreakAnkles();
if ((Object)(object)attacker != (Object)null)
{
Mod.Log.Msg("BREAK ANKLES! weaved " + ((Object)attacker).name + "'s attack");
}
}
private static void ApplyDash(Actor local, Actor attacker)
{
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
BodyHandeler bodyHandeler = local.bodyHandeler;
if ((Object)(object)bodyHandeler == (Object)null)
{
Mod.Log.Warning("Backdash skipped: no BodyHandeler on " + ((Object)local).name);
return;
}
Il2CppReferenceArray<Rigidbody> allRigidbodies = bodyHandeler.GetAllRigidbodies();
if (allRigidbodies == null || ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count == 0)
{
Mod.Log.Warning("Backdash skipped: no rigidbodies on " + ((Object)local).name);
return;
}
List<Rigidbody> list = new List<Rigidbody>(((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count);
for (int i = 0; i < ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count; i++)
{
Rigidbody val = ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies)[i];
if ((Object)(object)val != (Object)null)
{
list.Add(val);
}
}
if (list.Count != 0)
{
MelonCoroutines.Start(DashCoroutine(DashDirection(local, attacker), list));
}
}
private static IEnumerator DashCoroutine(Vector3 dir, List<Rigidbody> rbs)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
float dash = Mathf.Max(1f, Config.DashSpeed.Value);
float hop = Mathf.Max(0f, Config.HopSpeed.Value);
for (int f = 0; f < 18; f++)
{
float num = ((f < 8) ? 1f : (1f - (float)(f - 8) / 10f));
Vector3 velocity = dir * (dash * num) + Vector3.up * (hop * num);
try
{
for (int i = 0; i < rbs.Count; i++)
{
Rigidbody val = rbs[i];
if (!((Object)(object)val == (Object)null))
{
val.velocity = velocity;
}
}
}
catch
{
break;
}
yield return null;
}
}
private static Vector3 DashDirection(Actor local, Actor attacker)
{
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: 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_004f: 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_0071: Unknown result type (might be due to invalid IL or missing references)
Vector3 val;
if ((Object)(object)attacker != (Object)null)
{
val = ((Component)local).transform.position - ((Component)attacker).transform.position;
val.y = 0f;
if (((Vector3)(ref val)).sqrMagnitude < 0.001f)
{
val = -((Component)local).transform.forward;
}
}
else
{
val = -((Component)local).transform.forward;
}
if (!(((Vector3)(ref val)).sqrMagnitude > 0.001f))
{
return Vector3.back;
}
return ((Vector3)(ref val)).normalized;
}
private static Actor FindAttacker(Actor local)
{
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
List<Actor> cachedActors = Actor.CachedActors;
if (cachedActors == null || cachedActors.Count == 0)
{
return null;
}
Actor result = null;
float num = float.MaxValue;
float num2 = Mathf.Max(0f, Config.AttackRange.Value);
Vector3 position = ((Component)local).transform.position;
for (int i = 0; i < cachedActors.Count; i++)
{
Actor val = cachedActors[i];
if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)local)
{
continue;
}
ControlHandeler controlHandeler = val.controlHandeler;
if (controlHandeler != null && IsAttacking(controlHandeler))
{
float num3 = Vector3.Distance(((Component)val).transform.position, position);
if (!(num3 > num2) && num3 < num)
{
num = num3;
result = val;
}
}
}
return result;
}
private static bool IsAttacking(ControlHandeler ch)
{
if (!ch.leftPunch && !ch.rightPunch && !ch.leftKick && !ch.rightKick && !ch.headbutt && !ch.leftGrab)
{
return ch.rightGrab;
}
return true;
}
private static bool ReadDodgeInput(Actor local)
{
//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)
try
{
InputUser inputPlayer = local.InputPlayer;
ReadOnlyArray<InputDevice> pairedDevices = ((InputUser)(ref inputPlayer)).pairedDevices;
if (pairedDevices.Count > 0)
{
for (int i = 0; i < pairedDevices.Count; i++)
{
InputDevice val = pairedDevices[i];
if (val != null)
{
InputControl val2 = ((InputControl)val).TryGetChildControl(Config.GamepadButton.Value);
if (val2 == null)
{
val2 = ((InputControl)val).TryGetChildControl(Config.KeyboardButton.Value);
}
ButtonControl val3 = (ButtonControl)(object)((val2 is ButtonControl) ? val2 : null);
if (val3 != null && val3.isPressed)
{
return true;
}
}
}
}
}
catch
{
}
try
{
Keyboard current = Keyboard.current;
if (current != null && current.rightShiftKey != null && ((ButtonControl)current.rightShiftKey).isPressed)
{
return true;
}
Gamepad current2 = Gamepad.current;
if (current2 != null && current2.rightTrigger != null && current2.rightTrigger.isPressed)
{
return true;
}
}
catch
{
}
return false;
}
private static DodgeState GetState(Actor a)
{
DodgeState dodgeState = FindState(a);
if (dodgeState != null)
{
return dodgeState;
}
dodgeState = new DodgeState();
LocalList.Add(a);
StateList.Add(dodgeState);
return dodgeState;
}
private static DodgeState FindState(Actor a)
{
for (int i = 0; i < LocalList.Count; i++)
{
if ((Object)(object)LocalList[i] == (Object)(object)a)
{
return StateList[i];
}
}
return null;
}
private static bool IsLocalGame()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
try
{
GameManagerNew instance = GameManagerNew.Instance;
if ((Object)(object)instance == (Object)null)
{
return true;
}
long num = Convert.ToInt64(instance.CurrentGameType);
return num == 0L || num == 1;
}
catch
{
return true;
}
}
}
public class Mod : MelonMod
{
private bool _loggedError;
public static Mod Instance { get; private set; }
public static Instance Log => Melon<Mod>.Logger;
public override void OnInitializeMelon()
{
Instance = this;
try
{
Config.Init();
SoundBank.Init();
Patches.Apply(((MelonBase)this).HarmonyInstance);
Log.Msg("Break Ankles initialized!");
Log.Msg("Press Right Shift (keyboard) or RT (gamepad) to backdash (3s cooldown). Attacks during the dash deal 0 damage.");
Log.Msg("Drop .wav/.ogg/.mp3 files into:");
Log.Msg(" " + SoundBank.WeaveFolder + " -> dodge 'whoosh' sounds");
Log.Msg(" " + SoundBank.BreakFolder + " -> 'break ankles' sounds");
}
catch (Exception ex)
{
Log.Error("Break Ankles failed to initialize: " + ex);
}
}
public override void OnUpdate()
{
if (Instance != this)
{
return;
}
try
{
Dodge.Update();
}
catch (Exception ex)
{
if (!_loggedError)
{
_loggedError = true;
Log.Error("Break Ankles error in OnUpdate: " + ex);
}
}
}
}
public static class Patches
{
public static void Apply(Harmony harmony)
{
try
{
harmony.PatchAll(Assembly.GetExecutingAssembly());
Mod.Log.Msg("Harmony patches applied (damage weave active).");
}
catch (Exception ex)
{
Mod.Log.Warning("Could not apply Harmony patches, using invulnerability-flag weaving only: " + ex.Message);
}
}
}
[HarmonyPatch(typeof(StatusHandeler), "AddDamage")]
public static class AddDamagePatch
{
public static bool Prefix(StatusHandeler __instance)
{
return !Dodge.IsInvulnerable(__instance.actor);
}
}
public static class SoundBank
{
private sealed class SoundEntry
{
public string Path;
public bool IsWav;
public string Kind;
public SoundEntry(string path, bool isWav, string kind)
{
Path = path;
IsWav = isWav;
Kind = kind;
}
}
private const uint SND_FILENAME = 131072u;
private const uint SND_ASYNC = 1u;
private const uint SND_NODEFAULT = 2u;
private const uint PlayFlags = 131075u;
public static readonly string Root = Path.Combine(MelonEnvironment.UserDataDirectory, "BreakAnkles", "Sounds");
public static readonly string WeaveFolder = Path.Combine(Root, "Weave");
public static readonly string BreakFolder = Path.Combine(Root, "BreakAnkles");
private static readonly string CacheDir = Path.Combine(MelonEnvironment.UserDataDirectory, "BreakAnkles", "Cache");
private static readonly List<SoundEntry> WeaveSounds = new List<SoundEntry>();
private static readonly List<SoundEntry> BreakSounds = new List<SoundEntry>();
private static int _lastWeave = -1;
private static int _lastBreak = -1;
private static readonly HashSet<string> Warned = new HashSet<string>();
public static bool HasWeaveSounds => WeaveSounds.Count > 0;
public static bool HasBreakSounds => BreakSounds.Count > 0;
[DllImport("winmm.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool PlaySound(string pszSound, IntPtr hmod, uint fdwSound);
[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
private static extern int mciSendString(string command, StringBuilder ret, int retLen, IntPtr cb);
public static void Init()
{
try
{
Directory.CreateDirectory(CacheDir);
}
catch
{
}
MelonCoroutines.Start(LoadCoroutine());
}
private static IEnumerator LoadCoroutine()
{
yield return null;
if (Directory.Exists(WeaveFolder))
{
string[] files = Directory.GetFiles(WeaveFolder);
foreach (string file in files)
{
yield return LoadFileInto(file, "weave_", WeaveSounds);
}
}
if (Directory.Exists(BreakFolder))
{
string[] files = Directory.GetFiles(BreakFolder);
foreach (string file2 in files)
{
yield return LoadFileInto(file2, "ankle_", BreakSounds);
}
}
Mod.Log.Msg($"Loaded {WeaveSounds.Count} weave sound(s), {BreakSounds.Count} break-ankles sound(s).");
if (WeaveSounds.Count == 0 || BreakSounds.Count == 0)
{
Mod.Log.Warning("Missing sounds! Drop files into " + Root + " (folders 'Weave' and 'BreakAnkles') and restart the game.");
}
}
private static IEnumerator LoadFileInto(string file, string prefix, List<SoundEntry> pool)
{
string fileName = Path.GetFileName(file);
string text = SniffKind(file);
switch (text)
{
case "wav":
{
string text2 = CacheWav(file, prefix);
if (text2 != null)
{
pool.Add(new SoundEntry(text2, isWav: true, "wav"));
Mod.Log.Msg("Loaded sound: " + fileName);
}
break;
}
case "mp3":
case "m4a":
case "ogg":
case "media":
pool.Add(new SoundEntry(file, isWav: false, text));
Mod.Log.Msg("Loaded sound: " + fileName + " (" + text + ")");
break;
default:
Mod.Log.Warning("Skipped '" + fileName + "': unknown file type.");
break;
}
yield break;
}
private static string SniffKind(string path)
{
try
{
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
fileStream.Position = 0L;
byte[] array = new byte[12];
int num = fileStream.Read(array, 0, 12);
if (num >= 4 && array[0] == 82 && array[1] == 73 && array[2] == 70 && array[3] == 70)
{
return "wav";
}
if (num >= 3 && array[0] == 73 && array[1] == 68 && array[2] == 51)
{
return "mp3";
}
if (num >= 8 && array[4] == 102 && array[5] == 116 && array[6] == 121 && array[7] == 112)
{
return "m4a";
}
if (num >= 4 && array[0] == 79 && array[1] == 103 && array[2] == 103 && array[3] == 83)
{
return "ogg";
}
return "media";
}
catch
{
return "unknown";
}
}
private static string CacheWav(string source, string prefix)
{
try
{
if (!ParseWav(source, out var rate, out var channels, out var samples))
{
return null;
}
return WriteWav(prefix + Path.GetFileNameWithoutExtension(source) + ".wav", samples, channels, rate);
}
catch (Exception ex)
{
return WarnBad(source, ex.Message);
}
}
private static bool ParseWav(string path, out int rate, out ushort channels, out float[] samples)
{
rate = 0;
channels = 0;
samples = null;
byte[] array = File.ReadAllBytes(path);
if (array.Length < 44)
{
WarnBad(path, "file too small");
return false;
}
if (array[0] != 82 || array[1] != 73 || array[2] != 70 || array[3] != 70 || array[8] != 87 || array[9] != 65 || array[10] != 86 || array[11] != 69)
{
WarnBad(path, "not a RIFF/WAVE file");
return false;
}
int num = -1;
int num2 = -1;
int num3 = 0;
int num5;
for (int i = 12; i + 8 <= array.Length; i += num5)
{
string text = Encoding.ASCII.GetString(array, i, 4);
int num4 = BitConverter.ToInt32(array, i + 4);
if (text == "fmt ")
{
num = i + 8;
}
else if (text == "data")
{
num2 = i + 8;
num3 = num4;
break;
}
num5 = 8 + num4;
if (num5 <= 0)
{
break;
}
}
if (num < 0 || num2 < 0)
{
WarnBad(path, "missing fmt/data chunk");
return false;
}
if (num3 <= 0)
{
WarnBad(path, "empty data chunk");
return false;
}
ushort num6 = BitConverter.ToUInt16(array, num);
channels = BitConverter.ToUInt16(array, num + 2);
rate = BitConverter.ToInt32(array, num + 4);
ushort num7 = BitConverter.ToUInt16(array, num + 14);
if (channels == 0 || rate <= 0)
{
WarnBad(path, "bad header");
return false;
}
if (num6 != 1 && num6 != 3)
{
WarnBad(path, "compressed WAV not supported (use PCM .wav)");
return false;
}
int num8 = array.Length - num2;
if (num3 > num8)
{
num3 = num8;
}
int num9 = (num7 + 7) / 8;
if (num9 == 0)
{
WarnBad(path, "bad bit depth");
return false;
}
int num10 = num3 / (num9 * channels);
if (num10 <= 0)
{
WarnBad(path, "no samples");
return false;
}
samples = new float[num10 * channels];
if (num6 == 3)
{
if (num7 != 32)
{
WarnBad(path, "float WAV must be 32-bit");
return false;
}
int num11 = 0;
int num12 = num2;
while (num11 < samples.Length)
{
samples[num11] = BitConverter.ToSingle(array, num12);
num11++;
num12 += 4;
}
}
else
{
switch (num7)
{
case 8:
{
int num20 = 0;
int num21 = num2;
while (num20 < samples.Length)
{
samples[num20] = ((float)(int)array[num21] - 128f) / 128f;
num20++;
num21++;
}
break;
}
case 16:
{
int num15 = 0;
int num16 = num2;
while (num15 < samples.Length)
{
samples[num15] = (float)BitConverter.ToInt16(array, num16) / 32768f;
num15++;
num16 += 2;
}
break;
}
case 24:
{
int num17 = 0;
int num18 = num2;
while (num17 < samples.Length)
{
int num19 = array[num18] | (array[num18 + 1] << 8) | (array[num18 + 2] << 16);
if ((num19 & 0x800000) != 0)
{
num19 |= -16777216;
}
samples[num17] = (float)num19 / 8388608f;
num17++;
num18 += 3;
}
break;
}
case 32:
{
int num13 = 0;
int num14 = num2;
while (num13 < samples.Length)
{
samples[num13] = (float)BitConverter.ToInt32(array, num14) / 2.1474836E+09f;
num13++;
num14 += 4;
}
break;
}
default:
WarnBad(path, "unsupported bit depth " + num7);
return false;
}
}
return true;
}
private static string WriteWav(string cacheName, float[] samples, int channels, int rate)
{
if (samples == null || samples.Length == 0 || channels <= 0 || rate <= 0)
{
return null;
}
float num = 0f;
for (int i = 0; i < samples.Length; i++)
{
float num2 = Math.Abs(samples[i]);
if (num2 > num)
{
num = num2;
}
}
float num3 = ((num > 0.0001f && num < 0.9f) ? (0.9f / num) : 1f);
if (num3 > 1f)
{
num3 = 1f;
}
string text = Path.Combine(CacheDir, cacheName);
using FileStream output = new FileStream(text, FileMode.Create);
using BinaryWriter binaryWriter = new BinaryWriter(output);
int num4 = samples.Length * 2;
binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
binaryWriter.Write(36 + num4);
binaryWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
binaryWriter.Write(Encoding.ASCII.GetBytes("fmt "));
binaryWriter.Write(16);
binaryWriter.Write((short)1);
binaryWriter.Write((short)channels);
binaryWriter.Write(rate);
binaryWriter.Write(rate * channels * 2);
binaryWriter.Write((short)(channels * 2));
binaryWriter.Write((short)16);
binaryWriter.Write(Encoding.ASCII.GetBytes("data"));
binaryWriter.Write(num4);
for (int j = 0; j < samples.Length; j++)
{
float num5 = samples[j] * num3;
if (num5 < -1f)
{
num5 = -1f;
}
else if (num5 > 1f)
{
num5 = 1f;
}
binaryWriter.Write((short)(num5 * 32767f));
}
return text;
}
private static string WarnBad(string path, string why)
{
Mod.Log.Warning("Skipped '" + Path.GetFileName(path) + "': " + why);
return null;
}
public static void PlayWeave()
{
PlayRandom(WeaveSounds, ref _lastWeave, 0f, "bkweave");
}
public static void PlayBreakAnkles()
{
PlayRandom(BreakSounds, ref _lastBreak, 0.35f, "bkmeme");
}
private static void PlayRandom(List<SoundEntry> pool, ref int lastIdx, float delay, string mciAlias)
{
if (pool.Count == 0)
{
return;
}
int num;
if (pool.Count == 1)
{
num = 0;
}
else
{
num = Random.Range(0, pool.Count);
if (num == lastIdx)
{
num = (num + 1) % pool.Count;
}
}
lastIdx = num;
SoundEntry entry = pool[num];
if (delay <= 0f)
{
PlayNow(entry, mciAlias);
}
else
{
MelonCoroutines.Start(PlayDelayed(entry, mciAlias, delay));
}
}
private static IEnumerator PlayDelayed(SoundEntry entry, string mciAlias, float delay)
{
yield return (object)new WaitForSeconds(delay);
PlayNow(entry, mciAlias);
}
private static void PlayNow(SoundEntry entry, string mciAlias)
{
if (entry.IsWav)
{
try
{
PlaySound(entry.Path, IntPtr.Zero, 131075u);
return;
}
catch (Exception ex)
{
WarnOnce(entry, "could not play: " + ex.Message);
return;
}
}
try
{
mciSendString("close " + mciAlias, null, 0, IntPtr.Zero);
if (mciSendString("open \"" + entry.Path.Replace("\"", "") + "\" alias " + mciAlias + " type mpegvideo", null, 0, IntPtr.Zero) != 0 && mciSendString("open \"" + entry.Path.Replace("\"", "") + "\" alias " + mciAlias, null, 0, IntPtr.Zero) != 0)
{
WarnOnce(entry, "this format can't be played on this PC (" + entry.Kind + "). Convert or re-download it as .wav and it will work.");
}
else
{
mciSendString("play " + mciAlias, null, 0, IntPtr.Zero);
}
}
catch (Exception ex2)
{
WarnOnce(entry, "could not play: " + ex2.Message);
}
}
private static void WarnOnce(SoundEntry entry, string why)
{
if (Warned.Add(entry.Path))
{
Mod.Log.Warning("'" + Path.GetFileName(entry.Path) + "' " + why);
}
}
}
}