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 LobbyReactions v1.0.1
LobbyReactions.dll
Decompiled 3 hours 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.Globalization; using System.IO; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using LobbyReactions.Core; using LobbyReactions.Net; using LobbyReactions.Net.Commands; using LobbyReactions.Players; using LobbyReactions.UI; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.Audio; using UnityEngine.TextCore; 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: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("RED")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+46a9ecc61c0cc989f2f8eb583872cc48ef802219")] [assembly: AssemblyProduct("LobbyReactions")] [assembly: AssemblyTitle("LobbyReactions")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LobbyReactions.UI { internal static class FallbackFontRules { internal const string SharedName = "SP-CyrillicFallback"; internal static bool ShouldRegister(bool triedThisProcess, IEnumerable<string?>? globalFallbackNames) { if (triedThisProcess) { return false; } if (globalFallbackNames == null) { return true; } foreach (string globalFallbackName in globalFallbackNames) { if (string.Equals(globalFallbackName, "SP-CyrillicFallback", StringComparison.Ordinal)) { return false; } } return true; } internal static bool PumpsBeforeLobby(BridgeVerdict bridge) { return bridge == BridgeVerdict.Absent; } } internal enum WheelKeyVerdict { Usable, Off, NotKeyboard, GameKey, SharePermissionsKey } internal static class HotkeyRules { internal const int None = 0; internal const int Escape = 27; internal const int Mouse0 = 323; internal const int Mouse6 = 329; internal const int JoystickButton0 = 330; internal static bool IsKeyboardKey(int key) { if (key > 0) { switch (key) { default: return key < 330; case 27: case 323: case 324: case 325: case 326: case 327: case 328: case 329: break; } } return false; } internal static WheelKeyVerdict JudgeWheelKey(int wheel, bool gameUsesKey, bool takenBySharePermissions = false) { if (wheel == 0) { return WheelKeyVerdict.Off; } if (!IsKeyboardKey(wheel)) { return WheelKeyVerdict.NotKeyboard; } if (takenBySharePermissions) { return WheelKeyVerdict.SharePermissionsKey; } if (!gameUsesKey) { return WheelKeyVerdict.Usable; } return WheelKeyVerdict.GameKey; } internal static int EffectiveWheelKey(int configured, bool takenBySharePermissions) { if (!takenBySharePermissions) { return configured; } return 0; } internal static string Why(WheelKeyVerdict verdict) { return verdict switch { WheelKeyVerdict.Off => "is None (the wheel is off)", WheelKeyVerdict.NotKeyboard => "is not a key the wheel takes (Esc, a mouse or joystick button, F16 and up)", WheelKeyVerdict.GameKey => "is bound by R.E.P.O.", WheelKeyVerdict.SharePermissionsKey => "is a key SharePermissions uses", _ => "is usable", }; } } internal sealed class WheelKeyWarnings { private int refusedKey; private WheelKeyVerdict refusedWhy; internal bool ShouldWarn(int key, WheelKeyVerdict verdict) { if (verdict == WheelKeyVerdict.Usable) { refusedKey = 0; refusedWhy = WheelKeyVerdict.Usable; return false; } if (key == refusedKey && verdict == refusedWhy) { return false; } refusedKey = key; refusedWhy = verdict; return true; } } internal static class KeyText { private const string AlphaPrefix = "Alpha"; private const string KeypadPrefix = "Keypad"; private const string KeyboardDevice = "<Keyboard>"; internal static bool IsKeyboardBindingFor(string? bindingPath, string keyCodeName) { if (bindingPath == null) { return false; } int num = bindingPath.IndexOf('/'); if (num <= 0 || num == bindingPath.Length - 1) { return false; } if (!string.Equals(bindingPath.Substring(0, num), "<Keyboard>", StringComparison.OrdinalIgnoreCase)) { return false; } string a = bindingPath.Substring(num + 1); string[] array = ControlNames(keyCodeName); foreach (string b in array) { if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static string[] ControlNames(string keyCodeName) { string text = DigitAfter("Alpha", keyCodeName); if (text != null) { return new string[1] { text }; } if (keyCodeName.Length > "Keypad".Length && keyCodeName.StartsWith("Keypad", StringComparison.Ordinal)) { return new string[1] { "numpad" + keyCodeName.Substring("Keypad".Length) }; } switch (keyCodeName) { case "Return": return new string[1] { "enter" }; case "LeftShift": return new string[2] { "leftShift", "shift" }; case "RightShift": return new string[2] { "rightShift", "shift" }; case "LeftControl": return new string[2] { "leftCtrl", "ctrl" }; case "RightControl": return new string[2] { "rightCtrl", "ctrl" }; case "LeftAlt": return new string[2] { "leftAlt", "alt" }; case "RightAlt": case "AltGr": return new string[3] { "rightAlt", "altGr", "alt" }; case "LeftApple": case "LeftWindows": case "LeftCommand": case "LeftMeta": return new string[4] { "leftMeta", "leftWindows", "leftCommand", "leftApple" }; case "RightMeta": case "RightApple": case "RightWindows": case "RightCommand": return new string[4] { "rightMeta", "rightWindows", "rightCommand", "rightApple" }; case "Print": return new string[1] { "printScreen" }; case "Menu": return new string[1] { "contextMenu" }; default: return new string[1] { keyCodeName }; } } private static string? DigitAfter(string prefix, string keyCodeName) { if (keyCodeName.Length != prefix.Length + 1 || !keyCodeName.StartsWith(prefix, StringComparison.Ordinal) || keyCodeName[prefix.Length] < '0' || keyCodeName[prefix.Length] > '9') { return null; } return keyCodeName.Substring(prefix.Length); } } internal static class KeyUse { private static bool gameKeysUnreadableLogged; private static bool menuLibLooked; private static Func<bool>? menuLibFocus; private static bool menuLibFocusFailedLogged; private const string MenuLibAssembly = "MenuLib"; private const string MenuLibInputType = "MenuLib.MonoBehaviors.REPOInputStringSystem"; internal unsafe static bool GameUsesKey(KeyCode key) { try { return GameUsesKeyRaw(((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString()); } catch (Exception ex) { if (!gameKeysUnreadableLogged) { gameKeysUnreadableLogged = true; Plugin.Logger.LogWarning((object)("Game key bindings unreadable, the wheel key's check against them is skipped (logged once): " + ex.GetType().Name + ": " + ex.Message)); } return false; } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool GameUsesKeyRaw(string keyCodeName) { //IL_002e: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_003a: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) InputManager instance = InputManager.instance; if ((Object)(object)instance == (Object)null) { return false; } foreach (InputKey value in Enum.GetValues(typeof(InputKey))) { if ((int)value != 22 && KeyText.IsKeyboardBindingFor(instance.GetKeyString(value), keyCodeName)) { return true; } } foreach (MovementDirection value2 in Enum.GetValues(typeof(MovementDirection))) { if (instance.GetMovementBindingIndex(value2) >= 0 && KeyText.IsKeyboardBindingFor(instance.GetMovementKeyString(value2), keyCodeName)) { return true; } } return false; } internal static bool IsTyping() { if (MenuLibFieldFocused()) { return true; } MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && instance.textInputActive) { return true; } ChatManager instance2 = ChatManager.instance; if ((Object)(object)instance2 != (Object)null) { return instance2.chatActive; } return false; } private static bool MenuLibFieldFocused() { if (!menuLibLooked) { menuLibLooked = true; menuLibFocus = FindMenuLibFocus(); } if (menuLibFocus == null) { return false; } try { return menuLibFocus(); } catch (Exception ex) { menuLibFocus = null; if (!menuLibFocusFailedLogged) { menuLibFocusFailedLogged = true; Plugin.Logger.LogWarning((object)("MenuLib's text-field focus unreadable, the wheel no longer checks it (logged once): " + ex.GetType().Name + ": " + ex.Message)); } return false; } } private static Func<bool>? FindMenuLibFocus() { FieldInfo field = FindMenuLibFocusField(); if (field == null) { return null; } try { Func<bool> func = Expression.Lambda<Func<bool>>(Expression.Field(null, field), Array.Empty<ParameterExpression>()).Compile(); func(); return func; } catch (Exception ex) { Plugin.Logger.LogInfo((object)("MenuLib's text-field focus is read by reflection (" + ex.GetType().Name + ": " + ex.Message + ")")); return delegate { object value = field.GetValue(null); return value is bool && (bool)value; }; } } private static FieldInfo? FindMenuLibFocusField() { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name; try { name = assembly.GetName().Name; } catch (Exception) { continue; } if (!(name != "MenuLib")) { FieldInfo fieldInfo = assembly.GetType("MenuLib.MonoBehaviors.REPOInputStringSystem", throwOnError: false)?.GetField("hasAnyFocus", BindingFlags.Static | BindingFlags.Public); if (fieldInfo != null && fieldInfo.FieldType == typeof(bool)) { return fieldInfo; } Plugin.Logger.LogWarning((object)"MenuLib is loaded but has no static bool MenuLib.MonoBehaviors.REPOInputStringSystem.hasAnyFocus - the wheel cannot tell when a MenuLib text field has focus"); return null; } } } catch (Exception ex2) { Plugin.Logger.LogWarning((object)("Looking for MenuLib's text-field focus failed: " + ex2.GetType().Name + ": " + ex2.Message)); } return null; } } internal static class ModStyle { internal const int ReactionWheelSortingOrder = 110; internal const int ReactionStageSortingOrder = 105; internal const float ReactionWheelBackdropAlpha = 0.2f; internal static Color ReactionWheelColor(int rgb) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) return new Color((float)((rgb >> 16) & 0xFF) / 255f, (float)((rgb >> 8) & 0xFF) / 255f, (float)(rgb & 0xFF) / 255f); } internal static void ConfigureSingleLineEllipsis(TextMeshProUGUI label) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)label).enableWordWrapping = false; ((TMP_Text)label).overflowMode = (TextOverflowModes)1; ((TMP_Text)label).margin = new Vector4(0f, 0f - ((TMP_Text)label).fontSize, 0f, 0f - ((TMP_Text)label).fontSize); } } internal static class OverlayFont { private static TMP_FontAsset? nativeFont; private static Material? nativeFontMaterial; private static bool fallbackTried; private static float nextProbeAt; private static readonly string[] FallbackOsFonts = new string[3] { "Segoe UI", "Arial", "Tahoma" }; private static readonly string CyrillicAlphabet = BuildCyrillicAlphabet(); private static readonly List<Transform> ownRoots = new List<Transform>(); internal static TMP_FontAsset? Font => nativeFont; internal static int Generation { get; private set; } private static string BuildCyrillicAlphabet() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 1040; i <= 1103; i++) { stringBuilder.Append((char)i); } stringBuilder.Append('Ё').Append('ё'); return stringBuilder.ToString(); } internal static void RegisterOwnRoot(Transform root) { if ((Object)(object)root != (Object)null && !ownRoots.Contains(root)) { ownRoots.Add(root); } } internal static void Pump() { if ((Object)(object)nativeFont != (Object)null || Time.unscaledTime < nextProbeAt) { return; } nextProbeAt = Time.unscaledTime + 0.5f; try { ResolveRaw(); } catch { } if (!((Object)(object)nativeFont == (Object)null)) { try { RegisterGlobalFallbackRaw(); } catch (Exception ex) { Plugin.Logger.LogInfo((object)("Cyrillic fallback font not registered (" + ex.GetType().Name + ": " + ex.Message + ") - the wheel's text uses the game font only")); } Generation++; Plugin.Logger.LogInfo((object)("Overlay font resolved: " + ((Object)nativeFont).name)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ResolveRaw() { TMP_FontAsset val = null; TextMeshProUGUI[] array = Object.FindObjectsOfType<TextMeshProUGUI>(); foreach (TextMeshProUGUI val2 in array) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)((TMP_Text)val2).font == (Object)null) && !((Object)(object)((TMP_Asset)((TMP_Text)val2).font).material == (Object)null) && !IsOwnLabel(((TMP_Text)val2).transform)) { if (val == null) { val = ((TMP_Text)val2).font; } if ((Object)(object)((TMP_Text)val2).fontSharedMaterial != (Object)null && ((Object)((TMP_Text)val2).fontSharedMaterial).GetInstanceID() == ((Object)((TMP_Asset)((TMP_Text)val2).font).material).GetInstanceID()) { nativeFont = ((TMP_Text)val2).font; nativeFontMaterial = ((TMP_Asset)((TMP_Text)val2).font).material; return; } } } if (!((Object)(object)val == (Object)null)) { nativeFont = val; nativeFontMaterial = ((TMP_Asset)val).material; } } private static bool IsOwnLabel(Transform t) { foreach (Transform ownRoot in ownRoots) { if ((Object)(object)ownRoot != (Object)null && t.IsChildOf(ownRoot)) { return true; } } return false; } private static List<string?>? GlobalFallbackNames() { List<TMP_FontAsset> fallbackFontAssets = TMP_Settings.fallbackFontAssets; if (fallbackFontAssets == null) { return null; } List<string> list = new List<string>(fallbackFontAssets.Count); foreach (TMP_FontAsset item in fallbackFontAssets) { list.Add(((Object)(object)item == (Object)null) ? null : ((Object)item).name); } return list; } [MethodImpl(MethodImplOptions.NoInlining)] private static void RegisterGlobalFallbackRaw() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)nativeFont == (Object)null) { return; } bool flag = fallbackTried; fallbackTried = true; if (!FallbackFontRules.ShouldRegister(flag, GlobalFallbackNames())) { if (!flag) { Plugin.Logger.LogInfo((object)"Cyrillic fallback font already in TMP's global list (SP-CyrillicFallback, registered by SharePermissions) - using that one"); } return; } string[] pathsToOSFonts = Font.GetPathsToOSFonts(); string[] fallbackOsFonts = FallbackOsFonts; string text2 = default(string); foreach (string wanted in fallbackOsFonts) { string text = ResolveInstalledPath(pathsToOSFonts, wanted); if (text == null) { continue; } Font val = new Font(); try { Font.Internal_CreateFontFromPath(val, text); } catch (Exception ex) { Plugin.Logger.LogInfo((object)("Internal_CreateFontFromPath threw for " + text + ": " + ex.Message)); Object.Destroy((Object)(object)val); continue; } TMP_FontAsset val2 = TMP_FontAsset.CreateFontAsset(val); if ((Object)(object)val2 == (Object)null) { Plugin.Logger.LogInfo((object)("CreateFontAsset returned null: " + text)); Object.Destroy((Object)(object)val); continue; } ((Object)val2).hideFlags = (HideFlags)61; ((Object)val2).name = "SP-CyrillicFallback"; if ((Object)(object)nativeFontMaterial != (Object)null && (Object)(object)((TMP_Asset)val2).material != (Object)null) { ((TMP_Asset)val2).material.shader = nativeFontMaterial.shader; } if (!val2.TryAddCharacters(CyrillicAlphabet, ref text2, false)) { Plugin.Logger.LogInfo((object)("OS font missing Cyrillic characters [" + text2 + "]: " + text)); Object.Destroy((Object)(object)val2); Object.Destroy((Object)(object)val); continue; } ClampFace(val2); if (!AddToGlobalFallbacks(val2)) { Plugin.Logger.LogInfo((object)("Could not reach TMP_Settings.fallbackFontAssets - Cyrillic stays unavailable: " + text)); Object.Destroy((Object)(object)val2); Object.Destroy((Object)(object)val); continue; } ManualLogSource logger = Plugin.Logger; string text3 = $"Cyrillic fallback font registered globally: {text} (chars={val2.characterTable.Count} "; FaceInfo faceInfo = val2.faceInfo; logger.LogInfo((object)(text3 + $"pointSize={((FaceInfo)(ref faceInfo)).pointSize} globalList=[{DescribeGlobalFallbacks()}])")); return; } Plugin.Logger.LogInfo((object)"No OS fallback font found - the wheel's text uses the game font only"); } private static void ClampFace(TMP_FontAsset fallback) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) try { FaceInfo faceInfo = nativeFont.faceInfo; FaceInfo faceInfo2 = fallback.faceInfo; if (((FaceInfo)(ref faceInfo)).pointSize > 0 && ((FaceInfo)(ref faceInfo2)).pointSize > 0) { float num = ((FaceInfo)(ref faceInfo)).ascentLine / (float)((FaceInfo)(ref faceInfo)).pointSize; float num2 = ((FaceInfo)(ref faceInfo)).descentLine / (float)((FaceInfo)(ref faceInfo)).pointSize; bool flag = false; if (((FaceInfo)(ref faceInfo2)).ascentLine / (float)((FaceInfo)(ref faceInfo2)).pointSize > num) { ((FaceInfo)(ref faceInfo2)).ascentLine = num * (float)((FaceInfo)(ref faceInfo2)).pointSize; flag = true; } if (((FaceInfo)(ref faceInfo2)).descentLine / (float)((FaceInfo)(ref faceInfo2)).pointSize < num2) { ((FaceInfo)(ref faceInfo2)).descentLine = num2 * (float)((FaceInfo)(ref faceInfo2)).pointSize; flag = true; } if (flag) { fallback.faceInfo = faceInfo2; } } } catch (Exception ex) { Plugin.Logger.LogInfo((object)("Fallback face clamp skipped: " + ex.Message)); } } private static bool AddToGlobalFallbacks(TMP_FontAsset fallback) { List<TMP_FontAsset> list = TMP_Settings.fallbackFontAssets; if (list == null) { TMP_Settings instance = TMP_Settings.instance; if ((Object)(object)instance == (Object)null) { return false; } FieldInfo field = typeof(TMP_Settings).GetField("m_fallbackFontAssets", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) { return false; } list = new List<TMP_FontAsset>(); field.SetValue(instance, list); } if (!list.Contains(fallback)) { list.Insert(0, fallback); } return true; } private static string DescribeGlobalFallbacks() { List<TMP_FontAsset> fallbackFontAssets = TMP_Settings.fallbackFontAssets; if (fallbackFontAssets == null) { return "<null>"; } StringBuilder stringBuilder = new StringBuilder(fallbackFontAssets.Count * 32); stringBuilder.Append("n=").Append(fallbackFontAssets.Count); for (int i = 0; i < fallbackFontAssets.Count; i++) { stringBuilder.Append((i == 0) ? ": " : ", "); TMP_FontAsset val = fallbackFontAssets[i]; stringBuilder.Append(((Object)(object)val == (Object)null) ? "<null>" : (string.IsNullOrEmpty(((Object)val).name) ? "<unnamed>" : ((Object)val).name)); } return stringBuilder.ToString(); } private static string? ResolveInstalledPath(string[] paths, string wanted) { string text = wanted.Replace(" ", ""); string text2 = null; foreach (string text3 in paths) { string text4 = Path.GetFileNameWithoutExtension(text3).Replace(" ", ""); if (string.Equals(text4, text, StringComparison.OrdinalIgnoreCase)) { return text3; } if (text2 == null && text4.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { text2 = text3; } } return text2; } } internal static class PngHeader { internal const int MaxBytes = 1048576; internal const int Length = 24; private static readonly byte[] Signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 }; internal static bool TryReadSize(byte[]? png, out int width, out int height) { width = (height = 0); if (png == null || png.Length < 24) { return false; } for (int i = 0; i < Signature.Length; i++) { if (png[i] != Signature[i]) { return false; } } if (BigEndian(png, 8) != 13 || png[12] != 73 || png[13] != 72 || png[14] != 68 || png[15] != 82) { return false; } int num = BigEndian(png, 16); int num2 = BigEndian(png, 20); if (num <= 0 || num2 <= 0) { return false; } width = num; height = num2; return true; } internal static string? Refusal(byte[]? png, int size) { if (!TryReadSize(png, out var width, out var height)) { return "does not start with a PNG signature and an IHDR chunk"; } if (width != size || height != size) { return $"is {width}x{height} px, not {size}x{size}"; } return null; } private static int BigEndian(byte[] b, int at) { return (b[at] << 24) | (b[at + 1] << 16) | (b[at + 2] << 8) | b[at + 3]; } } internal static class ReactionAnimCore { internal enum Sprite { Coin, Sparkle, Shard, Droplet, Dust, Wisp, Star, Steam, Z, Question, Ember, Confetti, Mote } internal enum Channel { X, Y, ScaleX, ScaleY, Rotation, Red } internal enum Shape { Bump, Hop, Shake, Wave, Pulse, Rise, Spring, UiSpring, Keys } internal readonly struct Move { internal readonly Channel Channel; internal readonly float Start; internal readonly float Length; internal readonly float Amount; internal readonly float Hz; internal readonly Shape Shape; internal readonly float Damping; internal readonly float Hold; internal readonly Track? Track; internal Move(Channel channel, float start, float length, float amount, Shape shape, float hz, float damping = 0f, float hold = 0f, Track? track = null) { Channel = channel; Start = start; Length = length; Amount = amount; Shape = shape; Hz = hz; Damping = damping; Hold = hold; Track = track; } } internal sealed class Track { private readonly float[] times; private readonly float[] values; private readonly float offset; internal readonly bool Loop; internal readonly bool IsValid; internal Track(bool loop, params float[] pairs) : this(loop, Half(pairs, 0), Half(pairs, 1), 0f, pairs != null && pairs.Length % 2 == 0) { } private Track(bool loop, float[] times, float[] values, float offset, bool even) { Loop = loop; this.times = times; this.values = values; this.offset = offset; bool flag = even && times.Length >= 2 && times[0] == 0f; int num = 0; while (flag && num < times.Length) { flag = !float.IsNaN(values[num]) && !float.IsInfinity(values[num]) && !float.IsInfinity(times[num]) && (num == 0 || times[num] > times[num - 1]); num++; } IsValid = flag; } private static float[] Half(float[] pairs, int which) { if (pairs == null) { return new float[0]; } float[] array = new float[pairs.Length / 2]; for (int i = 0; i < array.Length; i++) { array[i] = pairs[2 * i + which]; } return array; } internal float At(float time) { if (!IsValid) { return 0f; } float num = times[times.Length - 1]; double num2 = (double)time + (double)offset; if (Loop) { num2 %= (double)num; if (num2 < 0.0) { num2 += (double)num; } } else { if (num2 <= 0.0) { return values[0]; } if (num2 >= (double)num) { return values[values.Length - 1]; } } for (int i = 1; i < times.Length; i++) { if (!(num2 > (double)times[i])) { double num3 = times[i - 1]; double num4 = (num2 - num3) / ((double)times[i] - num3); return (float)((double)values[i - 1] + (double)(values[i] - values[i - 1]) * num4); } } return values[values.Length - 1]; } internal Track From(float seconds) { return new Track(Loop, times, values, offset + seconds, IsValid); } } internal sealed class Emitter { internal Sprite Sprite; internal int Count = 6; internal float SpawnStart = 0.55f; internal float SpawnLength = 0.2f; internal float OriginX; internal float OriginY; internal float Angle; internal float Spread = 60f; internal bool Radial; internal float SpeedMin = 200f; internal float SpeedMax = 360f; internal float Gravity = 800f; internal float Spin = 180f; internal float LifeMin = 0.7f; internal float LifeMax = 1f; internal float Size = 48f; internal float ScaleStart = 1f; internal float ScaleEnd = 0.6f; internal float SwayAmp; internal float SwayHz; internal uint Tint = 16777215u; internal bool Palette; internal bool Sphere; internal Track? SizeOverLife; } internal sealed class Script { internal readonly string Code; internal readonly Move[] Moves; internal readonly Emitter[] Emitters; internal readonly Extent Reach; internal Script(string code, Move[] moves, Emitter[] emitters) { Code = code; Moves = moves; Emitters = emitters; Reach = MeasureReach(this); } } internal readonly struct Extent { internal readonly float Left; internal readonly float Right; internal readonly float Up; internal readonly float Down; internal Extent(float left, float right, float up, float down) { Left = left; Right = right; Up = up; Down = down; } } internal struct Particle { internal Sprite Sprite; internal float X; internal float Y; internal float Rotation; internal float Size; internal float Alpha; internal uint Tint; } internal sealed class Frame { internal int Phase; internal int FrameIndex; internal float Flight; internal float FlightScale; internal float X; internal float Y; internal float ScaleX; internal float ScaleY; internal float Rotation; internal float Alpha; internal float Red; internal readonly Particle[] Particles = new Particle[24]; internal int ParticleCount; } internal static class ClipTracks { internal static readonly Track SprintBounce = new Track(true, 0f, 1f, 0.017f, 0.89f, 0.033f, 0.6f, 0.083f, -0.6f, 0.1f, -0.89f, 0.117f, -1f, 0.133f, -0.85f, 0.15f, -0.48f, 0.183f, 0.48f, 0.2f, 0.85f, 0.217f, 1f, 0.233f, 0.85f, 0.25f, 0.48f, 0.283f, -0.48f, 0.3f, -0.85f, 0.317f, -1f, 0.333f, -0.85f, 0.35f, -0.48f, 0.383f, 0.48f, 0.4f, 0.85f, 0.417f, 1f); internal static readonly Track SprintRoll = new Track(true, 0f, 0f, 0.017f, -0.35f, 0.033f, -0.6f, 0.05f, -0.76f, 0.083f, -0.93f, 0.133f, -0.95f, 0.15f, -0.88f, 0.183f, -0.56f, 0.25f, 0.42f, 0.283f, 0.8f, 0.317f, 0.98f, 0.333f, 1f, 0.367f, 0.84f, 0.383f, 0.65f, 0.4f, 0.39f, 0.417f, 0f); internal static readonly Track LandSquash = new Track(false, 0f, -1f, 0.05f, 0f, 0.067f, 0.17f, 0.1f, 0.2f, 0.167f, 0.01f, 0.3f, 0f); internal static readonly Track StandBreath = new Track(true, 0f, 0.38f, 0.083f, 0.73f, 0.167f, 0.93f, 0.267f, 1f, 0.367f, 0.95f, 0.567f, 0.61f, 1.1f, -0.67f, 1.25f, -0.91f, 1.4f, -1f, 1.617f, -0.9f, 1.767f, -0.67f, 1.883f, -0.26f, 2f, 0.38f); internal static readonly Track CrouchBreath = new Track(true, 0f, 1f, 0.167f, 0.92f, 0.3f, 0.73f, 0.417f, 0.41f, 0.617f, -0.42f, 0.7f, -0.69f, 0.817f, -0.92f, 0.967f, -1f, 1.133f, -0.93f, 1.267f, -0.75f, 1.383f, -0.43f, 1.583f, 0.35f, 1.683f, 0.65f, 1.833f, 0.9f, 2f, 1f); internal static readonly Track CrouchMoveSway = new Track(true, 0f, 0f, 0.117f, -0.6f, 0.2f, -0.86f, 0.3f, -0.97f, 0.45f, -0.93f, 0.517f, -0.83f, 0.567f, -0.65f, 0.617f, -0.37f, 0.717f, 0.36f, 0.8f, 0.75f, 0.883f, 0.93f, 1f, 1f, 1.1f, 0.95f, 1.167f, 0.83f, 1.233f, 0.57f, 1.333f, 0f); internal static readonly Track DuckWaddleBob = new Track(true, 0f, 0.89f, 0.033f, 0.57f, 0.083f, -1f, 0.183f, -0.94f, 0.217f, -0.57f, 0.3f, 0.79f, 0.317f, 0.94f, 0.35f, 1f, 0.433f, 0.89f, 0.45f, 0.66f, 0.467f, 0.21f, 0.5f, -1f, 0.583f, -1f, 0.617f, -0.69f, 0.683f, 0.69f, 0.7f, 0.91f, 0.733f, 1f, 0.833f, 0.89f); internal static readonly Track DuckAttackThrash = new Track(true, 0f, 1f, 0.033f, 0.96f, 0.067f, 0.76f, 0.083f, 0.53f, 0.117f, -0.13f, 0.133f, -0.38f, 0.167f, -0.68f, 0.217f, -0.88f, 0.267f, -0.93f, 0.3f, -0.9f, 0.317f, -0.8f, 0.333f, -0.55f, 0.367f, 0.44f, 0.383f, 0.69f, 0.417f, 0.91f, 0.483f, 1f); internal static readonly Track DuckBiteSquash = new Track(true, 0f, 0f, 0.117f, 0f, 0.133f, -0.1f, 0.183f, -0.9f, 0.2f, -1f, 0.233f, 0f, 0.383f, 0f, 0.4f, -0.26f, 0.417f, -0.74f, 0.433f, -1f, 0.45f, -0.74f, 0.467f, -0.26f, 0.483f, 0f); internal static readonly Track DuckDetransSquash = new Track(true, 0f, 0f, 0.167f, -0.14f, 0.55f, -0.88f, 0.7f, -1f, 0.733f, -1f, 0.75f, -0.93f, 0.767f, -0.76f, 0.817f, -0.03f, 0.833f, 0.14f, 0.85f, 0.21f, 0.9f, 0f, 1.05f, 0f); internal static readonly Track BowtieNoticeRoll = new Track(true, 0f, 0f, 0.05f, 0.02f, 0.083f, 0.3f, 0.133f, 0.83f, 0.167f, 1f, 0.217f, 0.93f, 0.25f, 0.74f, 0.283f, 0.32f, 0.317f, -0.29f, 0.333f, -0.46f, 0.35f, -0.5f, 0.383f, -0.37f, 0.45f, 0.24f, 0.5f, 0.42f, 0.533f, 0.39f, 0.6f, 0.03f, 0.617f, 0f, 0.783f, 0f); internal static readonly Track BowtieScreamThrob = new Track(true, 0f, 0f, 0.05f, -0.02f, 0.1f, -0.13f, 0.133f, -0.36f, 0.15f, -0.67f, 0.167f, -0.87f, 0.217f, -1f, 0.267f, -0.95f, 0.3f, -0.05f, 0.367f, 0.05f, 0.4f, 0.04f, 0.433f, -0.07f, 0.45f, -0.22f, 0.467f, -0.56f, 0.483f, -0.82f, 0.5f, -0.93f, 0.567f, -0.99f, 0.583f, -0.95f, 0.617f, -0.03f, 0.633f, 0f); internal static readonly Track RobeShiver = new Track(true, 0f, 0f, 0.033f, 0.59f, 0.083f, 0.64f, 0.117f, -0.32f, 0.15f, -0.33f, 0.167f, -0.24f, 0.183f, 0.3f, 0.2f, 0.67f, 0.217f, 0.04f, 0.233f, -0.9f, 0.25f, -0.71f, 0.267f, 0f, 0.3f, 0.57f, 0.35f, 0.67f, 0.383f, -0.33f, 0.433f, -0.33f, 0.467f, 0.67f, 0.5f, -1f, 0.517f, 0f, 0.55f, 0.59f, 0.6f, 0.67f, 0.633f, -0.33f, 0.683f, 0f); internal static readonly Track MachineShake = new Track(true, 0f, 0.01f, 0.033f, -1f, 0.05f, -0.49f, 0.067f, 0.47f, 0.083f, 0.98f, 0.1f, 0.84f, 0.15f, -0.25f, 0.167f, -0.39f, 0.183f, -0.33f, 0.25f, 0.43f, 0.267f, 0.5f, 0.283f, 0.31f, 0.317f, -0.55f, 0.333f, -0.74f, 0.35f, -0.47f, 0.383f, 0.71f, 0.4f, 0.98f, 0.417f, 0.86f, 0.483f, -0.55f, 0.5f, -0.67f, 0.533f, -0.06f, 0.567f, 0.01f, 0.6f, -1f, 0.617f, -0.49f, 0.633f, 0.47f, 0.65f, 0.98f, 0.667f, 0.84f, 0.717f, -0.25f, 0.733f, -0.39f, 0.75f, -0.26f, 0.783f, 0.32f, 0.8f, 0.48f, 0.883f, 0.12f, 0.933f, 0.87f, 0.95f, 0.77f, 1f, 0.01f); internal static readonly Track CrouchDrop = new Track(false, 0f, 0f, 0.033f, -0.04f, 0.067f, -0.21f, 0.117f, -0.81f, 0.15f, -0.99f, 0.183f, -1f, 0.2f, -0.89f, 0.233f, -0.82f, 0.333f, -1f); internal static readonly Track CrouchRise = new Track(false, 0f, -1f, 0.017f, -0.94f, 0.033f, -0.77f, 0.083f, -0.1f, 0.1f, 0.03f, 0.117f, 0.08f, 0.15f, 0.09f, 0.25f, -0.03f, 0.333f, 0f); internal static readonly Track HunterAimBody = new Track(false, 0f, -0.5f, 0.1f, -0.58f, 0.133f, -0.52f, 0.3f, 0.68f, 0.333f, 0.85f, 0.383f, 0.98f, 0.433f, 0.99f, 0.483f, 0.85f, 0.5f, 0.71f, 0.533f, 0.31f, 0.55f, 0.26f, 0.7f, 0.33f, 0.833f, 0.31f); internal static readonly Track ThrowerThrow = new Track(false, 0f, 0f, 0.033f, -0.03f, 0.067f, -0.15f, 0.133f, -0.77f, 0.167f, -0.91f, 0.333f, -0.72f, 0.35f, -0.64f, 0.367f, -0.46f, 0.4f, 0.77f, 0.417f, 0.97f, 0.433f, 1f, 0.533f, 0.81f, 0.683f, 0.73f, 0.7f, 0.65f, 0.733f, 0.15f, 0.75f, 0.01f, 0.767f, -0.05f, 0.933f, 0f); internal static readonly Track UpscreamShudder = new Track(false, 0f, 0f, 0.017f, -0.33f, 0.05f, -0.47f, 0.067f, 0.22f, 0.083f, 0.51f, 0.117f, -0.72f, 0.133f, -1f, 0.15f, -0.96f, 0.183f, -0.44f, 0.2f, 0.12f, 0.217f, 0.14f, 0.25f, -0.32f, 0.267f, -0.74f, 0.283f, -0.76f, 0.317f, -0.41f, 0.333f, -0.07f, 0.383f, -0.22f, 0.433f, -0.19f, 0.467f, 0.11f, 0.517f, -0.18f, 0.617f, -0.07f, 0.667f, -0.39f, 0.75f, 0.29f, 0.817f, -0.28f, 0.883f, -0.06f, 0.917f, -0.1f, 0.967f, -0.38f, 0.983f, -0.21f); internal static readonly Track HeadBiteRear = new Track(false, 0f, 0f, 0.033f, -0.03f, 0.067f, -0.17f, 0.083f, -0.36f, 0.1f, -0.75f, 0.117f, -0.91f, 0.183f, -1f, 0.2f, -0.85f, 0.233f, -0.13f, 0.25f, 0.13f, 0.267f, 0.15f, 0.3f, 0f, 0.333f, -0.03f, 0.9f, 0f); internal static readonly Track TumbleMoveRoll = new Track(true, 0f, -1f, 0.017f, -0.91f, 0.033f, -0.67f, 0.05f, -0.23f, 0.067f, 0.28f, 0.083f, 0.69f, 0.1f, 0.92f, 0.117f, 1f, 0.133f, 0.96f, 0.15f, 0.77f, 0.167f, 0.43f, 0.2f, -0.53f, 0.217f, -0.89f, 0.233f, -1f); } internal static class ParticleCurves { internal static readonly Track LoveMoteSize = new Track(false, 0f, 0f, 0.017f, 0.1f, 0.05f, 0.66f, 0.067f, 0.83f, 0.1f, 0.97f, 0.133f, 1f, 0.2f, 0.91f, 0.3f, 0.56f, 0.35f, 0.45f, 0.533f, 0.57f, 0.683f, 0.2f, 0.817f, 0.28f, 1f, 0f); } internal const float Duration = 3f; internal const float LaunchEnd = 0.35f; internal const float WindUpEnd = 0.55f; internal const float MomentEnd = 1.9f; internal const float SettleEnd = 2.6f; internal const float StartScale = 0.15f; internal const int MaxParticles = 24; internal const float StickerPx = 280f; internal const float AnchorDx = 190f; internal const float AnchorDy = 70f; internal const float ArcHeight = 40f; private const float Hit = 0.55f; internal const float EdgeFade = 0.06f; internal const int ReachSamplesPerSecond = 600; internal const float ReachPad = 1f; private static readonly float[] ConfettiTimes = new float[3] { 0.02f, 0.16f, 0.49f }; private static readonly uint[] ConfettiColors = new uint[3] { 16777215u, 14942089u, 5766912u }; internal static readonly IReadOnlyDictionary<string, Script> Scripts = BuildScripts(); private static readonly int[] FallbackPhases = new int[3] { 1, 0, 2 }; private const float TwoPi = MathF.PI * 2f; private const float LookHz = 2.387324f; private const float LookDamping = 0.3f; private const float TiltHz = 2.387324f; private const float TiltDamping = 0.5f; private const float SideHz = 2.387324f; private const float SideDamping = 0.6f; private const float TiltHold = 0.25f; internal static uint Seed(int actor, float start) { return (uint)(actor * -1640531535) ^ (uint)(start * 1000f); } internal static void Anchor(Script s, float headX, float headY, float screenW, float screenH, out float ax, out float ay) { Anchor(s.Reach, 190f, headX, headY, screenW, screenH, out ax, out ay); } internal static void Anchor(Extent reach, float dx, float headX, float headY, float screenW, float screenH, out float ax, out float ay) { ax = Clamp(headX + dx, reach.Left, Math.Max(reach.Left, screenW - reach.Right)); ay = Clamp(headY + 70f, reach.Down, Math.Max(reach.Down, screenH - reach.Up)); } internal static int FrameToDraw(int phase, int index, Func<int, int, bool> gone, int[] phaseCounts, out int drawPhase) { drawPhase = -1; if (phase < 0 || phase > 2 || phaseCounts == null || phaseCounts.Length != 3) { return -1; } int num = phaseCounts[phase]; if (num > 0) { int num2 = Math.Max(0, Math.Min(index, num - 1)); for (int i = 0; i < num; i++) { int num3 = (num2 - i + num) % num; if (!gone(phase, num3)) { drawPhase = phase; return num3; } } } int[] fallbackPhases = FallbackPhases; foreach (int num4 in fallbackPhases) { if (num4 == phase) { continue; } for (int k = 0; k < phaseCounts[num4]; k++) { if (!gone(num4, k)) { drawPhase = num4; return k; } } } return -1; } internal static int FrameIndex(float elapsed, int count, float rate) { if (count <= 1 || !(rate > 0f) || !(elapsed > 0f)) { return 0; } double num = (double)elapsed * (double)rate + 0.001; if (num >= 2147483647.0) { return 0; } return (int)num % count; } internal static int HeldIndex(float elapsed, int count, float rate) { if (count <= 1 || !(rate > 0f) || !(elapsed > 0f)) { return 0; } double num = (double)elapsed * (double)rate + 0.001; if (!(num >= (double)(count - 1))) { return (int)num; } return count - 1; } internal static float MomentRate(int count, float rate, bool momentOnce) { if (!momentOnce) { return rate; } return (float)count / 1.3499999f; } internal static Dictionary<string, Script> Playable(IEnumerable<Script> scripts, List<string> refused) { Dictionary<string, Script> dictionary = new Dictionary<string, Script>(StringComparer.Ordinal); foreach (Script script in scripts) { if (Validate(script, out string error)) { dictionary[script.Code] = script; } else { refused.Add(script.Code + ": " + error); } } return dictionary; } internal static void StickerPosition(Frame f, float headX, float headY, float anchorX, float anchorY, out float x, out float y) { FlightPosition(f, headX, headY, anchorX, anchorY, out x, out y); x += f.X; y += f.Y; } internal static void FlightPosition(Frame f, float headX, float headY, float anchorX, float anchorY, out float x, out float y) { float flight = f.Flight; x = headX + (anchorX - headX) * flight; y = headY + (anchorY - headY) * flight + 40f * (float)Math.Sin(Math.PI * (double)flight); } internal static void Evaluate(Script s, float t, uint seed, Frame f, int[] phaseCounts, float rate, bool momentOnce = false) { t = Clamp(t, 0f, 3f); EvaluateSticker(s, t, f); int count = ((phaseCounts == null || phaseCounts.Length != 3) ? 1 : phaseCounts[f.Phase]); if (f.Phase == 0) { f.FrameIndex = HeldIndex(t, count, rate); } else if (f.Phase == 2) { f.FrameIndex = HeldIndex(t - 1.9f, count, rate); } else if (momentOnce) { f.FrameIndex = HeldIndex(t - 0.55f, count, MomentRate(count, rate, momentOnce: true)); } else { f.FrameIndex = FrameIndex(t - 0.55f, count, rate); } f.ParticleCount = 0; for (int i = 0; i < s.Emitters.Length; i++) { Emit(s.Emitters[i], i, t, seed, f); } } internal static void EvaluateMotion(Script s, float t, Frame f) { EvaluateSticker(s, Clamp(t, 0f, 3f), f); } private static void EvaluateSticker(Script s, float t, Frame f) { f.Phase = ((!(t < 0.55f)) ? ((t < 1.9f) ? 1 : 2) : 0); float flight; float num2; float alpha; if (t < 0.35f) { float num = t / 0.35f; flight = OutQuad(num); num2 = Lerp(0.15f, 1f, OutBack(num)); alpha = Clamp01(num / 0.35f); } else if (t < 2.6f) { flight = 1f; num2 = 1f; alpha = 1f; } else { float num3 = (t - 2.6f) / 0.4000001f; flight = 1f - InQuad(num3); num2 = Lerp(1f, 0.15f, InQuad(num3)); alpha = 1f - Clamp01((num3 - 0.4f) / 0.6f); } float num4 = num2; float num5 = num2; float num6 = 0f; float num7 = 0f; float num8 = 0f; float num9 = 0f; Move[] moves = s.Moves; for (int i = 0; i < moves.Length; i++) { Move m = moves[i]; if (!(t < m.Start) && !(t > m.Start + m.Length)) { float u = ((m.Length > 0f) ? ((t - m.Start) / m.Length) : 1f); float num10 = m.Amount * ShapeAt(in m, u, t - m.Start); switch (m.Channel) { case Channel.X: num6 += num10; break; case Channel.Y: num7 += num10; break; case Channel.ScaleX: num4 *= 1f + num10; break; case Channel.ScaleY: num5 *= 1f + num10; break; case Channel.Rotation: num8 += num10; break; case Channel.Red: num9 += num10; break; } } } f.Flight = flight; f.FlightScale = num2; f.X = num6; f.Y = num7; f.ScaleX = num4; f.ScaleY = num5; f.Rotation = num8; f.Alpha = alpha; f.Red = Clamp01(num9); } private static Extent MeasureReach(Script s) { Frame frame = new Frame(); double num = 140.0; float num2 = 0f; float num3 = 0f; float num4 = 0f; float num5 = 0f; int num6 = (int)Math.Round(210.0); int num7 = (int)Math.Round(1560.0); for (int i = num6; i <= num7; i++) { EvaluateSticker(s, (float)i / 600f, frame); double num8 = (double)frame.Rotation * Math.PI / 180.0; double num9 = Math.Abs(Math.Cos(num8)); double num10 = Math.Abs(Math.Sin(num8)); double num11 = num * (double)Math.Abs(frame.ScaleX); double num12 = num * (double)Math.Abs(frame.ScaleY); float num13 = (float)(num11 * num9 + num12 * num10); float num14 = (float)(num11 * num10 + num12 * num9); num2 = Math.Max(num2, num13 - frame.X); num3 = Math.Max(num3, num13 + frame.X); num5 = Math.Max(num5, num14 - frame.Y); num4 = Math.Max(num4, num14 + frame.Y); } return new Extent(num2 + 1f, num3 + 1f, num4 + 1f, num5 + 1f); } internal static bool Validate(Script s, out string error) { Move[] moves = s.Moves; for (int i = 0; i < moves.Length; i++) { Move move = moves[i]; if (move.Start < 0f || move.Length < 0f || move.Start + move.Length > 3.0001f) { error = "a move outside the 3 s"; return false; } if (!Enum.IsDefined(typeof(Shape), move.Shape)) { error = "an unknown shape"; return false; } if (move.Shape == Shape.Rise && move.Start != 0f) { error = "a Rise that does not start at 0 s"; return false; } if (move.Shape == Shape.Spring && (!(move.Hz > 0f) || !(move.Damping >= 0f) || move.Damping >= 1f || !(move.Hold >= 0f))) { error = "a spring without a frequency, with a damping outside 0 to 1, or a negative hold"; return false; } if (move.Shape == Shape.Keys && (move.Track == null || !move.Track.IsValid)) { error = "a keyed move without a valid track"; return false; } } Emitter[] emitters = s.Emitters; foreach (Emitter emitter in emitters) { if (!Enum.IsDefined(typeof(Sprite), emitter.Sprite)) { error = "an unknown sprite"; return false; } if (emitter.Count <= 0 || emitter.LifeMin <= 0f || emitter.LifeMax < emitter.LifeMin) { error = "an emitter without particles or life"; return false; } if (emitter.SizeOverLife != null && !emitter.SizeOverLife.IsValid) { error = "an emitter with a size curve that is not valid"; return false; } if (emitter.SpawnStart + emitter.SpawnLength + 0.02f + emitter.LifeMax > 3.0001f) { error = "particles outliving the reaction"; return false; } } for (int k = 0; k <= 180; k++) { if (LiveCount(s, (float)k / 60f, 1u) > 24) { error = "more than " + 24 + " particles at once"; return false; } } error = ""; return true; } private static int LiveCount(Script s, float t, uint seed) { int num = 0; for (int i = 0; i < s.Emitters.Length; i++) { Emitter emitter = s.Emitters[i]; for (int j = 0; j < emitter.Count; j++) { Spawn(emitter, i, j, seed, out var spawn, out var life, out var _); float num2 = t - spawn; if (num2 >= 0f && num2 <= life) { num++; } } } return num; } private static void Spawn(Emitter em, int emitter, int i, uint seed, out float spawn, out float life, out uint h) { h = Hash(seed, (uint)emitter, (uint)i); float num = ((em.Count == 1) ? 0f : ((float)i / (float)(em.Count - 1))); spawn = em.SpawnStart + em.SpawnLength * num + 0.04f * (Rand(h, 1u) - 0.5f); life = Lerp(em.LifeMin, em.LifeMax, Rand(h, 2u)); } private static void Emit(Emitter em, int emitter, float t, uint seed, Frame f) { for (int i = 0; i < em.Count; i++) { if (f.ParticleCount >= 24) { break; } Spawn(em, emitter, i, seed, out var spawn, out var life, out var h); float num = t - spawn; if (!(num < 0f) && !(num > life)) { float num2 = (em.Radial ? (em.Angle + 360f * (float)i / (float)em.Count) : (em.Angle + em.Spread * (Rand(h, 3u) - 0.5f))); float num3 = Lerp(em.SpeedMin, em.SpeedMax, Rand(h, 4u)); if (em.Sphere) { float num4 = 2f * Rand(h, 8u) - 1f; num3 *= (float)Math.Sqrt(1f - num4 * num4); } double num5 = (double)num2 * Math.PI / 180.0; float num6 = (float)Math.Sin(num5) * num3; float num7 = (float)Math.Cos(num5) * num3; float num8 = num / life; ref Particle reference = ref f.Particles[f.ParticleCount++]; reference.Sprite = em.Sprite; reference.X = em.OriginX + num6 * num + em.SwayAmp * (float)Math.Sin(Math.PI * 2.0 * (double)em.SwayHz * (double)num + 6.2832 * (double)Rand(h, 5u)); reference.Y = em.OriginY + num7 * num - 0.5f * em.Gravity * num * num; reference.Rotation = em.Spin * (Rand(h, 6u) * 2f - 1f) * num; float num9 = ((em.SizeOverLife != null) ? em.SizeOverLife.At(num8) : Lerp(em.ScaleStart, em.ScaleEnd, num8)); reference.Size = em.Size * num9 * Lerp(0.8f, 1.2f, Rand(h, 7u)); reference.Alpha = Clamp01(Math.Min(1f, num / 0.08f) * ((num8 < 0.7f) ? 1f : (1f - (num8 - 0.7f) / 0.3f))); reference.Tint = (em.Palette ? ConfettiAt(num8) : em.Tint); } } } private static uint ConfettiAt(float u) { int num = ConfettiTimes.Length - 1; if (!(u > ConfettiTimes[0])) { return ConfettiColors[0]; } if (u >= ConfettiTimes[num]) { return ConfettiColors[num]; } int i; for (i = 1; u > ConfettiTimes[i]; i++) { } float num2 = (u - ConfettiTimes[i - 1]) / (ConfettiTimes[i] - ConfettiTimes[i - 1]); uint num3 = ConfettiColors[i - 1]; uint num4 = ConfettiColors[i]; uint num5 = 0u; for (int num6 = 16; num6 >= 0; num6 -= 8) { float num7 = (num3 >> num6) & 0xFF; float num8 = (num4 >> num6) & 0xFF; num5 |= (uint)Math.Round(num7 + (num8 - num7) * num2) << num6; } return num5; } private static float ShapeAt(in Move m, float u, float dt) { float hz = m.Hz; switch (m.Shape) { case Shape.Bump: return (float)Math.Sin(Math.PI * (double)u); case Shape.Hop: return (float)Math.Abs(Math.Sin(Math.PI * (double)hz * (double)dt)) * (1f - u); case Shape.Shake: return (float)Math.Sin(Math.PI * 2.0 * (double)hz * (double)dt) * (1f - u); case Shape.Wave: return (float)(Math.Sin(Math.PI * 2.0 * (double)hz * (double)dt) * Math.Sin(Math.PI * (double)u)); case Shape.Pulse: return (float)Math.Pow(Math.Max(0.0, Math.Sin(Math.PI * 2.0 * (double)hz * (double)dt)), 3.0) * (1f - u); case Shape.Spring: return SpringAt(dt, hz, m.Damping, m.Hold) * EaseOut(m.Length - dt); case Shape.UiSpring: return (float)(Math.Sin((double)hz * (1.0 - (double)u) * (double)u * Math.PI * 2.0) * (1.0 - (double)u)); case Shape.Keys: if (m.Track == null) { return 0f; } return m.Track.At(dt * ((hz > 0f) ? hz : 1f)) * EaseOut(dt) * EaseOut(m.Length - dt); default: return 1f - OutCubic(u); } } private static float EaseOut(float left) { float num = Clamp01(left / 0.06f); return num * num * (3f - 2f * num); } private static float SpringAt(float dt, float hz, float damping, float hold) { if (!(dt > 0f) || !(hz > 0f) || damping < 0f || damping >= 1f) { return 0f; } double num = Math.PI * 2.0 * (double)hz; double wd = num * Math.Sqrt(1.0 - (double)(damping * damping)); double a = (double)damping * num; if (hold <= 0f) { double num2 = Math.Atan2(wd, a) / wd; return (float)(Math.Exp((0.0 - a) * (double)dt) * Math.Sin(wd * (double)dt) / (Math.Exp((0.0 - a) * num2) * Math.Sin(wd * num2))); } return (float)(Step(dt) - Step(dt - hold)); double Step(double x) { if (!(x <= 0.0)) { return 1.0 - Math.Exp((0.0 - a) * x) * (Math.Cos(wd * x) + a / wd * Math.Sin(wd * x)); } return 0.0; } } private static uint Hash(uint seed, uint a, uint b) { uint num = (uint)(((int)seed * -1640531535) ^ ((int)(a + 2135587861) * -2048144777) ^ ((int)(b + 374761393) * -1028477379)); num ^= num >> 15; num *= 739982445; num ^= num >> 12; num *= 695872825; return num ^ (num >> 15); } private static float Rand(uint h, uint salt) { uint num = h ^ (salt * 668265263); num ^= num >> 16; num *= 2146121005; num ^= num >> 15; num *= 2221713035u; num ^= num >> 16; return (float)(num & 0xFFFFFF) / 16777216f; } private static float Lerp(float a, float b, float u) { return a + (b - a) * u; } private static float Clamp(float v, float lo, float hi) { if (!(v < lo)) { if (!(v > hi)) { return v; } return hi; } return lo; } private static float Clamp01(float v) { return Clamp(v, 0f, 1f); } private static float OutQuad(float u) { return 1f - (1f - u) * (1f - u); } private static float InQuad(float u) { return u * u; } private static float OutCubic(float u) { return 1f - (1f - u) * (1f - u) * (1f - u); } private static float OutBack(float u) { float num = u - 1f; return 1f + 2.70158f * num * num * num + 1.70158f * num * num; } private static Move M(Channel channel, float start, float length, float amount, Shape shape, float hz = 0f) { return new Move(channel, start, length, amount, shape, hz); } private static Move K(Channel channel, float start, float length, float amount, Track track, float speed = 0f) { return new Move(channel, start, length, amount, Shape.Keys, speed, 0f, 0f, track); } private static Move S(Channel channel, float start, float length, float amount, float hz, float damping, float hold = 0f) { return new Move(channel, start, length, amount, Shape.Spring, hz, damping, hold); } private static Move[] Cat(params Move[][] parts) { int num = 0; foreach (Move[] array in parts) { num += array.Length; } Move[] array2 = new Move[num]; int num2 = 0; foreach (Move[] array3 in parts) { array3.CopyTo(array2, num2); num2 += array3.Length; } return array2; } private static Move[] Quake(float at, float shake, float shakeHold, float impact, float impactHold) { float length = shakeHold + shake / 10f; float length2 = impactHold + impact / 30f; return new Move[4] { M(Channel.Rotation, at, length, 0.5f * shake, Shape.Shake, 6f), M(Channel.X, at, length, 2f * shake, Shape.Shake, 5.3f), M(Channel.Y, at, length, 1.5f * shake, Shape.Shake, 6.7f), M(Channel.X, at, length2, 1.5f * impact, Shape.Shake, 20f) }; } private static Move Jolt(float at, float impact, float impactHold) { return M(Channel.X, at, impactHold + impact / 30f, 1.5f * impact, Shape.Shake, 20f); } private static Dictionary<string, Script> BuildScripts() { Dictionary<string, Script> d = new Dictionary<string, Script>(StringComparer.Ordinal); Add("loot", new Move[4] { K(Channel.Y, 0.55f, 1.3499999f, 4f, ClipTracks.SprintBounce), K(Channel.Rotation, 0.55f, 1.3499999f, 1.5f, ClipTracks.SprintRoll), S(Channel.Rotation, 0.55f, 0.7f, -5f, 2.387324f, 0.5f, 0.25f), S(Channel.Rotation, 1.9f, 0.7f, -3.5f, 2.387324f, 0.5f, 0.25f) }, new Emitter[5] { CoinHop(0.6541667f), CoinHop(0.8625f), CoinHop(1.0708333f), CoinHop(1.2791667f), new Emitter { Sprite = Sprite.Sparkle, Count = 4, SpawnStart = 0.6f, SpawnLength = 1f, OriginX = -45f, OriginY = 0f, Spread = 360f, SpeedMin = 40f, SpeedMax = 100f, Gravity = 0f, Spin = 90f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 36f, ScaleStart = 0.4f, ScaleEnd = 1.1f } }); Add("oops", new Move[4] { M(Channel.X, 0.55f, 0.2f, 3f, Shape.Shake, 20f), M(Channel.Y, 0.55f, 0.2f, 2f, Shape.Shake, 17f), S(Channel.Y, 0.55f, 1f, -4f, 2.387324f, 0.3f), S(Channel.Y, 1.9f, 1f, -3f, 2.387324f, 0.3f) }, new Emitter[1] { new Emitter { Sprite = Sprite.Shard, Count = 8, SpawnLength = 0.08f, OriginX = -85f, OriginY = -80f, Spread = 150f, SpeedMin = 260f, SpeedMax = 440f, Gravity = 1300f, Spin = 720f, LifeMin = 0.7f, LifeMax = 1f, Size = 42f, ScaleEnd = 0.9f } }); Add("fire", new Move[5] { K(Channel.Y, 0.55f, 1.3499999f, 5f, ClipTracks.SprintBounce), K(Channel.Rotation, 0.55f, 1.3499999f, 1.5f, ClipTracks.SprintRoll), S(Channel.Rotation, 0.55f, 0.7f, -5f, 2.387324f, 0.5f, 0.25f), K(Channel.ScaleY, 1.9f, 0.3f, 0.073f, ClipTracks.LandSquash), K(Channel.ScaleX, 1.9f, 0.3f, -0.068f, ClipTracks.LandSquash) }, new Emitter[2] { new Emitter { Sprite = Sprite.Ember, Count = 16, SpawnLength = 1.3f, OriginX = -50f, OriginY = 100f, Spread = 50f, SpeedMin = 90f, SpeedMax = 170f, Gravity = -120f, Spin = 0f, LifeMin = 0.7f, LifeMax = 1f, Size = 30f, ScaleEnd = 0.3f, SwayAmp = 12f, SwayHz = 2f }, new Emitter { Sprite = Sprite.Sparkle, Count = 4, SpawnLength = 0.1f, OriginX = -50f, OriginY = 95f, Spread = 360f, SpeedMin = 80f, SpeedMax = 160f, Gravity = 0f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 34f, ScaleStart = 0.5f, ScaleEnd = 1f } }); Add("run", new Move[3] { K(Channel.Y, 0.35f, 2.25f, 5f, ClipTracks.SprintBounce.From(0.2167f)), K(Channel.Rotation, 0.35f, 2.25f, 1.5f, ClipTracks.SprintRoll.From(0.2167f)), S(Channel.Rotation, 0.55f, 0.7f, -5f, 2.387324f, 0.5f, 0.25f) }, new Emitter[2] { new Emitter { Sprite = Sprite.Droplet, Count = 4, SpawnLength = 0.15f, OriginX = -90f, OriginY = 55f, Angle = 50f, Spread = 50f, SpeedMin = 180f, SpeedMax = 260f, Gravity = 900f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 30f, Tint = 10475775u }, new Emitter { Sprite = Sprite.Dust, Count = 9, SpawnStart = 0.65000004f, SpawnLength = 1.6664f, OriginX = -90f, OriginY = -95f, Angle = 60f, Spread = 70f, SpeedMin = 40f, SpeedMax = 90f, Gravity = -30f, Spin = 60f, LifeMin = 0.45f, LifeMax = 0.6f, Size = 56f, ScaleStart = 0.5f, ScaleEnd = 1.3f } }); Add("help", new Move[2] { S(Channel.Y, 0.55f, 0.9f, -3f, 2.387324f, 0.3f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }, new Emitter[2] { new Emitter { Sprite = Sprite.Dust, Count = 5, SpawnStart = 1f, SpawnLength = 0.06f, OriginX = -30f, OriginY = -92f, Spread = 170f, SpeedMin = 80f, SpeedMax = 170f, Gravity = -30f, Spin = 60f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 56f, ScaleStart = 0.5f, ScaleEnd = 1.3f }, new Emitter { Sprite = Sprite.Droplet, Count = 3, SpawnStart = 1.9f, SpawnLength = 0.15f, OriginX = 55f, OriginY = -35f, Spread = 140f, SpeedMin = 160f, SpeedMax = 240f, Gravity = 900f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 28f, Tint = 10475775u } }); Add("rip", new Move[0], new Emitter[3] { new Emitter { Sprite = Sprite.Wisp, Count = 6, SpawnStart = 0.6f, SpawnLength = 1.3f, OriginX = -28f, OriginY = 30f, Spread = 40f, SpeedMin = 40f, SpeedMax = 80f, Gravity = -20f, Spin = 30f, LifeMin = 0.9f, LifeMax = 1f, Size = 56f, ScaleStart = 0.7f, ScaleEnd = 1.2f, SwayAmp = 18f, SwayHz = 1.2f }, new Emitter { Sprite = Sprite.Dust, Count = 2, SpawnStart = 91f / 120f, SpawnLength = 0f, OriginX = -20f, OriginY = -100f, Spread = 140f, SpeedMin = 80f, SpeedMax = 160f, Gravity = 600f, LifeMin = 0.45f, LifeMax = 0.6f, Size = 40f, ScaleEnd = 0.7f, Tint = 9071178u }, new Emitter { Sprite = Sprite.Dust, Count = 2, SpawnStart = 1.3833333f, SpawnLength = 0f, OriginX = -20f, OriginY = -100f, Spread = 140f, SpeedMin = 80f, SpeedMax = 160f, Gravity = 600f, LifeMin = 0.45f, LifeMax = 0.6f, Size = 40f, ScaleEnd = 0.7f, Tint = 9071178u } }); Add("scared", new Move[1] { K(Channel.ScaleX, 0.55f, 2.05f, 0.02f, ClipTracks.RobeShiver) }, new Emitter[1] { new Emitter { Sprite = Sprite.Droplet, Count = 6, SpawnLength = 0.3f, OriginX = 75f, OriginY = -15f, Spread = 150f, SpeedMin = 200f, SpeedMax = 300f, Gravity = 1000f, LifeMin = 0.5f, LifeMax = 0.8f, Size = 28f, Tint = 10475775u } }); Add("gg", new Move[0], new Emitter[3] { new Emitter { Sprite = Sprite.Confetti, Count = 12, SpawnLength = 0.15f, OriginY = 40f, Spread = 120f, SpeedMin = 340f, SpeedMax = 540f, Gravity = 650f, Spin = 720f, LifeMin = 1.1f, LifeMax = 1.3f, Size = 30f, ScaleEnd = 0.9f, Palette = true }, new Emitter { Sprite = Sprite.Confetti, Count = 6, SpawnStart = 1.4495502f, SpawnLength = 0.1f, OriginY = 40f, Spread = 100f, SpeedMin = 300f, SpeedMax = 480f, Gravity = 650f, Spin = 720f, LifeMin = 1f, LifeMax = 1.2f, Size = 28f, ScaleEnd = 0.9f, Palette = true }, new Emitter { Sprite = Sprite.Sparkle, Count = 5, SpawnStart = 0.75f, SpawnLength = 1f, OriginX = 28f, OriginY = 10f, Spread = 360f, SpeedMin = 50f, SpeedMax = 120f, Gravity = 0f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 36f, ScaleStart = 0.5f, ScaleEnd = 1f } }); float num = 1.037125f; float num2 = 1.4499999f; Add("clap", Cat(Quake(num, 2f, 0.1f, 2f, 0.05f), Quake(num2, 2f, 0.1f, 2f, 0.05f), new Move[2] { K(Channel.Rotation, num, 0.5f, 1.5f, ClipTracks.MachineShake), K(Channel.Rotation, num2, 0.5f, 1.5f, ClipTracks.MachineShake) }), new Emitter[5] { new Emitter { Sprite = Sprite.Sparkle, Count = 3, SpawnStart = 0.55f, SpawnLength = 0.3f, OriginX = -10f, OriginY = 30f, Spread = 360f, SpeedMin = 30f, SpeedMax = 70f, Gravity = 0f, Spin = 90f, LifeMin = 0.3f, LifeMax = 0.4f, Size = 30f, ScaleStart = 0.4f, ScaleEnd = 1f }, new Emitter { Sprite = Sprite.Sparkle, Count = 3, SpawnStart = 1f, SpawnLength = 0.3f, OriginX = -75f, OriginY = -85f, Spread = 360f, SpeedMin = 30f, SpeedMax = 70f, Gravity = 0f, Spin = 90f, LifeMin = 0.3f, LifeMax = 0.4f, Size = 30f, ScaleStart = 0.4f, ScaleEnd = 1f }, new Emitter { Sprite = Sprite.Confetti, Count = 7, SpawnStart = num2, SpawnLength = 0.08f, OriginX = 40f, OriginY = 120f, Angle = -30f, Spread = 50f, SpeedMin = 380f, SpeedMax = 560f, Gravity = 650f, Spin = 720f, LifeMin = 1.1f, LifeMax = 1.3f, Size = 30f, ScaleEnd = 0.9f, Palette = true }, new Emitter { Sprite = Sprite.Confetti, Count = 7, SpawnStart = num2, SpawnLength = 0.08f, OriginX = 85f, OriginY = 120f, Angle = 30f, Spread = 50f, SpeedMin = 380f, SpeedMax = 560f, Gravity = 650f, Spin = 720f, LifeMin = 1.1f, LifeMax = 1.3f, Size = 30f, ScaleEnd = 0.9f, Palette = true }, new Emitter { Sprite = Sprite.Sparkle, Count = 3, SpawnStart = num2, SpawnLength = 0.2f, OriginX = 60f, OriginY = 110f, Spread = 360f, SpeedMin = 80f, SpeedMax = 160f, Gravity = 0f, LifeMin = 0.6f, LifeMax = 0.8f, Size = 34f, ScaleStart = 0.5f, ScaleEnd = 1f, Palette = true } }); Add("laugh", new Move[12] { S(Channel.Y, 0.55f, 0.9f, -3f, 2.387324f, 0.3f), K(Channel.ScaleY, Ha(0), 0.3f, 0.05f, ClipTracks.LandSquash), K(Channel.ScaleX, Ha(0), 0.3f, -0.045f, ClipTracks.LandSquash), K(Channel.ScaleY, Ha(2), 0.3f, 0.05f, ClipTracks.LandSquash), K(Channel.ScaleX, Ha(2), 0.3f, -0.045f, ClipTracks.LandSquash), K(Channel.ScaleY, Ha(3), 0.3f, 0.05f, ClipTracks.LandSquash), K(Channel.ScaleX, Ha(3), 0.3f, -0.045f, ClipTracks.LandSquash), K(Channel.ScaleY, Ha(5), 0.3f, 0.05f, ClipTracks.LandSquash), K(Channel.ScaleX, Ha(5), 0.3f, -0.045f, ClipTracks.LandSquash), K(Channel.ScaleY, Ha(6), 0.3f, 0.05f, ClipTracks.LandSquash), K(Channel.ScaleX, Ha(6), 0.3f, -0.045f, ClipTracks.LandSquash), S(Channel.Y, 1.9f, 0.7f, -2f, 2.387324f, 0.3f) }, new Emitter[4] { Tears(Ha(0)), Tears(Ha(2)), Tears(Ha(3)), Tears(Ha(5)) }); float num3 = 0.67f; Add("angry", Cat(Quake(0.383f, 3f, 0.42f, 3f, 0.1f), Quake(0.55f, 3f, 0.6f, 2f, 0.1f), Quake(1.2f, 3f, 0.5f, 2f, 0.1f), new Move[11] { K(Channel.Rotation, 0f, 0.65f, 8f, ClipTracks.BowtieNoticeRoll), Jolt(0.5f, 3f, 0.1f), K(Channel.ScaleY, num3, 1.9f - num3, 0.08f, ClipTracks.BowtieScreamThrob), K(Channel.ScaleX, num3, 1.9f - num3, -0.04f, ClipTracks.BowtieScreamThrob), Jolt(num3 + 0.167f, 2f, 0.1f), Jolt(num3 + 0.3f, 2f, 0.1f), Jolt(num3 + 0.483f, 2f, 0.1f), Jolt(num3 + 0.617f, 2f, 0.1f), Jolt(num3 + 0.8f, 2f, 0.1f), Jolt(num3 + 0.933f, 2f, 0.1f), Jolt(num3 + 1.117f, 2f, 0.1f) }), new Emitter[2] { new Emitter { Sprite = Sprite.Steam, Count = 4, SpawnLength = 0.4f, OriginX = -80f, OriginY = 20f, Angle = -55f, Spread = 30f, SpeedMin = 140f, SpeedMax = 220f, Gravity = -60f, Spin = 60f, LifeMin = 0.6f, LifeMax = 0.9f, Size = 64f, ScaleStart = 0.6f, ScaleEnd = 1.6f }, new Emitter { Sprite = Sprite.Steam, Count = 4, SpawnLength = 0.4f, OriginX = 80f, OriginY = 20f, Angle = 55f, Spread = 30f, SpeedMin = 140f, SpeedMax = 220f, Gravity = -60f, Spin = 60f, LifeMin = 0.6f, LifeMax = 0.9f, Size = 64f, ScaleStart = 0.6f, ScaleEnd = 1.6f } }); Add("love", new Move[6] { K(Channel.Y, 0.35f, 2.65f, 3f, ClipTracks.StandBreath.From(0.2f)), S(Channel.Y, 0.55f, 2.45f, 3f, 2.387324f, 0.3f, 2.05f), M(Channel.Y, Word(0), 0.2f, 6f, Shape.UiSpring, 5f), M(Channel.Y, Word(1), 0.2f, 6f, Shape.UiSpring, 5f), M(Channel.Y, Word(2), 0.2f, 6f, Shape.UiSpring, 5f), M(Channel.Y, Word(3), 0.2f, 6f, Shape.UiSpring, 5f) }, new Emitter[1] { new Emitter { Sprite = Sprite.Mote, Count = 8, SpawnStart = 0.4f, SpawnLength = 1.35f, OriginX = -18f, OriginY = -14f, Spread = 360f, Sphere = true, SpeedMin = 45f, SpeedMax = 70f, Gravity = 0f, Spin = 0f, LifeMin = 0.9f, LifeMax = 1.2f, Size = 30f, SizeOverLife = ParticleCurves.LoveMoteSize, SwayAmp = 6f, SwayHz = 1f } }); Add("sleepy", new Move[1] { K(Channel.Y, 0.35f, 2.65f, 3f, ClipTracks.CrouchBreath.From(1.8f)) }, new Emitter[1] { new Emitter { Sprite = Sprite.Z, Count = 4, SpawnStart = 0.6f, SpawnLength = 1.35f, OriginX = 40f, OriginY = 90f, Angle = 20f, Spread = 10f, SpeedMin = 60f, SpeedMax = 80f, Gravity = 0f, Spin = 20f, LifeMin = 0.9f, LifeMax = 1f, Size = 44f, ScaleStart = 0.6f, ScaleEnd = 1.3f, SwayAmp = 14f, SwayHz = 1.2f } }); Add("question", new Move[2] { S(Channel.Rotation, 0.55f, 1.95f, -4f, 2.387324f, 0.6f, 1.3499999f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }, new Emitter[1] { new Emitter { Sprite = Sprite.Question, Count = 3, SpawnLength = 0.35f, OriginX = 47f, OriginY = 99f, Spread = 80f, SpeedMin = 160f, SpeedMax = 220f, Gravity = 300f, Spin = 60f, LifeMin = 0.7f, LifeMax = 0.9f, Size = 46f, ScaleStart = 0.4f, ScaleEnd = 1f } }); float num4 = 0.9f; Add("duck", Cat(Quake(num4 + 0.1f, 2f, 0.5f, 2f, 0.1f), new Move[9] { K(Channel.Y, 0f, 0.6f, 5f, ClipTracks.DuckWaddleBob), K(Channel.Rotation, num4 + 0.11f, 0.966f, 6f, ClipTracks.DuckAttackThrash.From(0.11f)), K(Channel.ScaleY, num4, 1.076f, 0.04f, ClipTracks.DuckBiteSquash), K(Channel.ScaleX, num4, 1.076f, -0.02f, ClipTracks.DuckBiteSquash), Jolt(num4 + 0.333f, 2f, 0.1f), Jolt(num4 + 0.583f, 2f, 0.1f), Jolt(num4 + 0.817f, 2f, 0.1f), K(Channel.ScaleY, 1.9f, 0.5f, 0.1f, ClipTracks.DuckDetransSquash.From(0.5f)), K(Channel.ScaleX, 1.9f, 0.5f, -0.05f, ClipTracks.DuckDetransSquash.From(0.5f)) }), new Emitter[2] { new Emitter { Sprite = Sprite.Dust, Count = 5, SpawnStart = 0.667f, SpawnLength = 0.08f, OriginY = 50f, Spread = 160f, SpeedMin = 120f, SpeedMax = 220f, Gravity = -30f, Spin = 90f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 54f, ScaleStart = 0.5f, ScaleEnd = 1.3f }, new Emitter { Sprite = Sprite.Droplet, Count = 5, SpawnStart = num4 + 0.1f, SpawnLength = 0.4f, OriginY = 40f, Spread = 120f, SpeedMin = 160f, SpeedMax = 260f, Gravity = 900f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 26f, Tint = 10475775u } }); Add("bonk", Cat(Quake(0.55f, 5f, 0.5f, 5f, 0.2f), Quake(1.9f, 3f, 0.5f, 5f, 0.1f)), new Emitter[3] { new Emitter { Sprite = Sprite.Steam, Count = 5, SpawnStart = 0.70000005f, SpawnLength = 0.3f, OriginX = -40f, OriginY = 45f, Spread = 90f, SpeedMin = 50f, SpeedMax = 110f, Gravity = -60f, Spin = 60f, LifeMin = 0.9f, LifeMax = 1.2f, Size = 52f, ScaleStart = 0.5f, ScaleEnd = 1.4f }, new Emitter { Sprite = Sprite.Star, Count = 5, SpawnStart = 1.2249999f, SpawnLength = 0f, Radial = true, OriginX = -80f, OriginY = -50f, SpeedMin = 160f, SpeedMax = 160f, Gravity = 0f, LifeMin = 0.45f, LifeMax = 0.5f, Size = 32f, ScaleEnd = 0.3f }, new Emitter { Sprite = Sprite.Star, Count = 4, SpawnStart = 1.9f, SpawnLength = 0.4f, OriginX = -100f, OriginY = -80f, Spread = 360f, SpeedMin = 40f, SpeedMax = 70f, Gravity = 0f, Spin = 240f, LifeMin = 0.5f, LifeMax = 0.55f, Size = 30f, ScaleStart = 0.6f, ScaleEnd = 1f, SwayAmp = 20f, SwayHz = 1.5f } }); Add("taxed", Cat(Drop(0.55f), Drop(1f), Drop(1.4499999f)), new Emitter[3] { Drain(0.55f), Drain(1f), Drain(1.4499999f) }); Add("tiny", new Move[1] { K(Channel.Rotation, 0.22f, 2f, 2.5f, ClipTracks.CrouchMoveSway) }, new Emitter[2] { new Emitter { Sprite = Sprite.Sparkle, Count = 3, SpawnStart = 0.85300004f, SpawnLength = 0f, OriginX = -84f, OriginY = -60f, Spread = 180f, SpeedMin = 40f, SpeedMax = 100f, Gravity = 0f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 32f, ScaleStart = 0.5f, ScaleEnd = 1f }, new Emitter { Sprite = Sprite.Sparkle, Count = 3, SpawnStart = 1.52f, SpawnLength = 0f, OriginX = -84f, OriginY = -60f, Spread = 180f, SpeedMin = 40f, SpeedMax = 100f, Gravity = 0f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 32f, ScaleStart = 0.5f, ScaleEnd = 1f } }); float[] array = new float[5] { Stomp(0), Stomp(2), Stomp(3), Stomp(5), Stomp(6) }; List<Move> list = new List<Move>(); List<Emitter> list2 = new List<Emitter>(); float[] array2 = array; foreach (float num5 in array2) { list.AddRange(Quake(num5, 2f, 0.1f, 1f, 0.05f)); list.Add(K(Channel.ScaleY, num5, 0.3f, 0.05f, ClipTracks.LandSquash)); list.Add(K(Channel.ScaleX, num5, 0.3f, -0.045f, ClipTracks.LandSquash)); list2.Add(new Emitter { Sprite = Sprite.Dust, Count = 3, SpawnStart = num5, SpawnLength = 0.04f, OriginX = -10f, OriginY = -112f, Spread = 160f, SpeedMin = 70f, SpeedMax = 140f, Gravity = -30f, Spin = 60f, LifeMin = 0.35f, LifeMax = 0.45f, Size = 44f, ScaleStart = 0.5f, ScaleEnd = 1.2f }); } Add("mad", list.ToArray(), list2.ToArray()); Add("shh", new Move[2] { K(Channel.Rotation, 0.55f, 1.3499999f, 2f, ClipTracks.HunterAimBody.From(0.05f), 0.833f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }, Array.Empty<Emitter>()); float num6 = 1.5625f; float num7 = 88f / 135f; float start = 1.2249999f - 0.43f / num7; Add("yoink", new Move[5] { S(Channel.Y, 0.55f, 0.9f, -3f, 2.387324f, 0.3f), K(Channel.Rotation, start, 0.933f / num7, 3f, ClipTracks.ThrowerThrow, num7), Jolt(num6, 10f, 0.1f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f), K(Channel.Rotation, 1.9f, 0.6999999f, 2f, ClipTracks.TumbleMoveRoll.From(0.1f)) }, new Emitter[1] { new Emitter { Sprite = Sprite.Shard, Count = 8, SpawnStart = num6, SpawnLength = 0f, Radial = true, Angle = 20f, OriginX = 33f, OriginY = 41f, SpeedMin = 260f, SpeedMax = 420f, Gravity = 1300f, Spin = 720f, LifeMin = 0.7f, LifeMax = 0.95f, Size = 40f, ScaleEnd = 0.85f } }); List<Move> list3 = new List<Move>(); for (int j = 0; j < 3; j++) { float num8 = 0.55f + 2f * (float)j * (25f / 111f); list3.Add(K(Channel.ScaleY, num8 - 25f / 111f, 0.2852252f, 0.04f, ClipTracks.CrouchDrop, 1.479852f)); list3.Add(K(Channel.ScaleY, num8, 0.2852252f, 0.04f, ClipTracks.CrouchRise, 0.888f)); } Add("teabag", list3.ToArray(), Array.Empty<Emitter>()); Add("yeeted", Quake(0.55f, 6f, 0.1f, 5f, 0.1f), new Emitter[1] { new Emitter { Sprite = Sprite.Dust, Count = 6, SpawnStart = 0.55f, SpawnLength = 0.06f, OriginX = 65f, OriginY = -115f, Spread = 170f, SpeedMin = 120f, SpeedMax = 220f, Gravity = -30f, Spin = 90f, LifeMin = 0.45f, LifeMax = 0.6f, Size = 42f, ScaleStart = 0.5f, ScaleEnd = 1.2f } }); Add("boo", Cat(Quake(1.4499999f, 2f, 0.5f, 1f, 0.1f), new Move[3] { S(Channel.Y, 0.55f, 0.9f, -3f, 2.387324f, 0.3f), K(Channel.Rotation, 0.55f, 0.8f, 2.5f, ClipTracks.UpscreamShudder.From(0.15f)), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }), Array.Empty<Emitter>()); Add("heavy", new Move[4] { K(Channel.Rotation, 0.55f, 1f, 2f, ClipTracks.CrouchMoveSway.From(0.867f)), K(Channel.Rotation, 1.55f, 0.3499999f, 2f, ClipTracks.CrouchMoveSway.From(0.867f)), S(Channel.Y, 0.55f, 0.9f, -2.5f, 2.387324f, 0.3f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }, new Emitter[5] { Strain(0.55f, 95f, -5f, 2), Strain(0.65000004f, -105f, 0f, 2), Strain(1.2166667f, 95f, -5f, 3), Strain(1.9f, 95f, -5f, 2), Strain(1.9499999f, -105f, 0f, 2) }); List<Move> list4 = new List<Move>(); for (int k = 0; k < 6; k++) { list4.Add(S(Channel.Rotation, 0.45000002f + (float)k * 0.24213074f, 0.6f, (k % 2 == 0) ? 1.5f : (-1.5f), 2.387324f, 0.5f, 0.24213074f)); } Add("aaaaa", list4.ToArray(), Array.Empty<Emitter>()); List<Move> list5 = new List<Move>(); for (int l = 0; l < 3; l++) { list5.Add(K(Channel.X, 0.55f + 0.5f * (float)l, Math.Min(0.5f, 1.3499999f - 0.5f * (float)l), 6f, ClipTracks.HeadBiteRear.From(0.05f), 0.4f)); } list5.Add(S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f)); Add("mybad", list5.ToArray(), new Emitter[1] { new Emitter { Sprite = Sprite.Droplet, Count = 3, SpawnStart = 1.9f, SpawnLength = 0.1f, OriginX = 85f, OriginY = 25f, Spread = 140f, SpeedMin = 180f, SpeedMax = 260f, Gravity = 900f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 28f, Tint = 10475775u } }); Add("holdthis", Cat(Quake(0.55f, 5f, 0.5f, 8f, 0.2f), new Move[2] { S(Channel.Y, 0.32154343f, 0.9f, -3f, 2.387324f, 0.3f), S(Channel.Y, 1.9f, 0.9f, -3f, 2.387324f, 0.3f) }), new Emitter[2] { new Emitter { Sprite = Sprite.Steam, Count = 6, SpawnStart = 1.2249999f, SpawnLength = 0.35f, OriginX = 14f, OriginY = 50f, Spread = 120f, SpeedMin = 50f, SpeedMax = 110f, Gravity = -60f, Spin = 60f, LifeMin = 0.9f, LifeMax = 1.2f, Size = 60f, ScaleStart = 0.6f, ScaleEnd = 1.5f }, new Emitter { Sprite = Sprite.Star, Count = 4, SpawnStart = 1.9f, SpawnLength = 0.4f, OriginX = 96f, OriginY = 2f, Spread = 360f, SpeedMin = 40f, SpeedMax = 70f, Gravity = 0f, Spin = 240f, LifeMin = 0.5f, LifeMax = 0.55f, Size = 30f, ScaleStart = 0.6f, ScaleEnd = 1f, SwayAmp = 20f, SwayHz = 1.5f } }); List<Move> list6 = new List<Move> { K(Channel.Y, 0.35f, 2.65f, 2f, ClipTracks.StandBreath.From(0.2f)) }; for (int m = 0; m < 5; m++) { list6.Add(S(Channel.Y, 0.55f + (float)m * (20f / 69f), 0.9f, -2f, 2.387324f, 0.3f)); } list6.Add(S(Channel.Y, 1.9f, 0.9f, -2f, 2.387324f, 0.3f)); list6.Add(S(Channel.Rotation, 1.9f, 1.1f, 2f, 2.387324f, 0.5f, 0.6999999f)); Add("bruh", list6.ToArray(), Array.Empty<Emitter>()); Add("sus", new Move[4] { K(Channel.Y, 0.35f, 2.65f, 2f, ClipTracks.StandBreath.From(0.2f)), S(Channel.Y, 0.55f, 0.9f, -2.5f, 2.387324f, 0.3f), S(Channel.Y, 1.2249999f, 0.9f, -2.5f, 2.387324f, 0.3f), S(Channel.Rotation, 1.9f, 1.1f, -1.5f, 2.387324f, 0.5f, 0.6999999f) }, Array.Empty<Emitter>()); return d; void Add(string code, Move[] moves, params Emitter[] emitters) { d[code] = new Script(code, moves, emitters); } static Emitter CoinHop(float at) { return new Emitter { Sprite = Sprite.Coin, Count = 2, SpawnStart = at, SpawnLength = 0f, OriginX = -45f, OriginY = 5f, Spread = 70f, SpeedMin = 240f, SpeedMax = 340f, Gravity = 1100f, Spin = 540f, LifeMin = 0.6f, LifeMax = 0.75f, Size = 36f, ScaleEnd = 0.8f }; } static Emitter Drain(float at) { return new Emitter { Sprite = Sprite.Coin, Count = 4, SpawnStart = at, SpawnLength = 0.15f, OriginX = -90f, OriginY = -84f, Angle = 180f, Spread = 50f, SpeedMin = 60f, SpeedMax = 140f, Gravity = 700f, Spin = 360f, LifeMin = 0.6f, LifeMax = 0.8f, Size = 40f, ScaleEnd = 0.7f }; } static Move[] Drop(float at) { return new Move[4] { M(Channel.Y, at, 0.3f, 6f, Shape.UiSpring, 10f), M(Channel.ScaleX, at, 0.2f, 0.04f, Shape.UiSpring, 5f), M(Channel.ScaleY, at, 0.2f, 0.04f, Shape.UiSpring, 5f), M(Channel.Red, at, 0.25f, 0.5f, Shape.Bump) }; } static float Ha(int num9) { return 0.55f + (float)num9 / 4.55f; } static float Stomp(int num9) { return 0.55f + (float)num9 / 4.55f; } static Emitter Strain(float at, float x, float y, int n) { return new Emitter { Sprite = Sprite.Droplet, Count = n, SpawnStart = at, SpawnLength = 0.1f, OriginX = x, OriginY = y, Spread = 140f, SpeedMin = 140f, SpeedMax = 220f, Gravity = 900f, LifeMin = 0.45f, LifeMax = 0.6f, Size = 24f, Tint = 10475775u }; } static Emitter Tears(float at) { return new Emitter { Sprite = Sprite.Droplet, Count = 2, SpawnStart = at, SpawnLength = 0f, OriginX = 70f, OriginY = 100f, Spread = 140f, SpeedMin = 200f, SpeedMax = 280f, Gravity = 900f, LifeMin = 0.5f, LifeMax = 0.7f, Size = 26f, Tint = 12577279u }; } static float Word(int num9) { return 0.55f + (float)num9 / 2.5f; } } } internal static class ReactionArt { internal const string StickerPrefix = "LobbyReactions.ReactionStickers."; internal const string ParticlePrefix = "LobbyReactions.ReactionParticles."; internal const string SoundPrefix = "LobbyReactions.ReactionSounds."; internal const int StickerSize = 320; internal const int ParticleSize = 96; internal static readonly IReadOnlyDictionary<string, string[][]> Frames = new Dictionary<string, string[][]>(StringComparer.Ordinal) { ["loot"] = new string[3][] { new string[2] { "loot_a_1.png", "loot_a_2.png" }, new string[4] { "loot_b_1.png", "loot_b_2.png", "loot_b_3.png", "loot_b_4.png" }, new string[2] { "loot_c_1.png", "loot_c_2.png" } }, ["fire"] = new string[3][] { new string[2] { "fire_a_1.png", "fire_a_2.png" }, new string[4] { "fire_b_1.png", "fire_b_2.png", "fire_b_3.png", "fire_b_4.png" }, new string[1] { "fire_c_1.png" } }, ["oops"] = new string[3][] { new string[2] { "oops_a_1.png", "oops_a_2.png" }, new string[3] { "oops_b_1.png", "oops_b_2.png", "oops_b_3.png" }, new string[2] { "oops_c_1.png", "oops_c_2.png" } }, ["help"] = new string[3][] { new string[1] { "help_a_1.png" }, new string[3] { "help_b_1.png", "help_b_2.png", "help_b_3.png" }, new string[1] { "help_c_1.png" } }, ["scared"] = new string[3][] { new string[2] { "scared_a_1.png", "scared_a_2.png" }, new string[3] { "scared_b_1.png", "scared_b_2.png", "scared_b_3.png" }, new string[1] { "scared_c_1.png" } }, ["run"] = new string[3][] { new string[1] { "run_a_1.png" }, new string[4] { "run_b_1.png", "run_b_2.png", "run_b_3.png", "run_b_4.png" }, new string[1] { "run_c_1.png" } }, ["rip"] = new string[3][] { new string[1] { "rip_a_1.png" }, new string[3] { "rip_b_1.png", "rip_b_2.png", "rip_b_3.png" }, new string[2] { "rip_c_1.png", "rip_c_2.png" } }, ["gg"] = new string[3][] { new string[1] { "gg_a_1.png" }, new string[3] { "gg_b_1.png", "gg_b_2.png", "gg_b_3.png" }, new string[2] { "gg_c_1.png", "gg_c_2.png" } }, ["clap"] = new string[3][] { new string[1] { "clap_a_1.png" }, new string[3] { "clap_b_1.png", "clap_b_2.png", "clap_b_3.png" }, new string[2] { "clap_c_1.png", "clap_c_2.png" } }, ["laugh"] = new string[3][] { new string[1] { "laugh_a_1.png" }, new string[3] { "laugh_b_1.png", "laugh_b_2.png", "laugh_b_3.png" }, new string[1] { "laugh_c_1.png" } }, ["love"] = new string[3][] { new string[1] { "love_a_1.png" }, new string[3] { "love_b_1.png", "love_b_2.png", "love_b_3.png" }, new string[1] { "love_c_1.png" } }, ["sleepy"] = new string[3][] { new string[1] { "sleepy_a_1.png" }, new string[2] { "sleepy_b_1.png", "sleepy_b_2.png" }, new string[1] { "sleepy_c_1.png" } }, ["question"] = new string[3][] { new string[1] { "question_a_1.png" }, new string[2] { "question_b_1.png", "question_b_2.png" }, new string[2] { "question_c_1.png", "question_c_2.png" } }, ["mad"] = new string[3][] { new string[1] { "mad_a_1.png" }, new string[3] { "mad_b_1.png", "mad_b_2.png", "mad_b_3.png" }, new string[1] { "mad_c_1.png" } }, ["angry"] = new string[3][] { new string[1] { "angry_a_1.png" }, new string[3] { "angry_b_1.png", "angry_b_2.png", "angry_b_3.png" }, new string[1] { "angry_c_1.png" } }, ["duck"] = new string[3][] { new string[2] { "duck_a_1.png", "duck_a_2.png" }, new string[3] { "duck_b_1.png", "duck_b_2.png", "duck_b_3.png" }, new string[2] { "duck_c_1.png", "duck_c_2.png" } }, ["boo"] = new string[3][] { new string[1] { "boo_a_1.png" }, new string[3] { "boo_b_1.png", "boo_b_2.png", "boo_b_3.png" }, new string[1] { "boo_c_1.png" } }, ["aaaaa"] = new string[3][] { new string[1] { "aaaaa_a_1.png" }, new string[2] { "aaaaa_b_1.png", "aaaaa_b_2.png" }, new string[1] { "aaaaa_c_1.png" } }, ["yeeted"] = new string[3][] { new string[3] { "yeeted_a_1.png", "yeeted_a_2.png", "yeeted_a_3.png" }, new string[1] { "yeeted_b_1.png" }, new string[1] { "yeeted_c_1.png" } }, ["mybad"] = new string[3][] { new string[1] { "mybad_a_1.png" }, new string[2] { "mybad_b_1.png", "mybad_b_2.png" }, new string[1] { "mybad_c_1.png" } }, ["holdthis"] = new string[3][] { new string[2] { "holdthis_a_1.png", "holdthis_a_2.png" }, new string[2] { "holdthis_b_1.png", "holdthis_b_2.png" }, new string[1] { "holdthis_c_1.png" } }, ["bonk"] = new string[3][] { new string[2] { "bonk_a_1.png", "bonk_a_2.png" }, new string[2] { "bonk_b_1.png", "bonk_b_2.png" }, new string[1] { "bonk_c_1.png" } }, ["teabag"] = new string[3][] { new string[1] { "teabag_a_1.png" }, new string[2] { "teabag_b_1.png", "teabag_b_2.png" }, new string[1] { "teabag_c_1.png" } }, ["sus"] = new string[3][] { new string[1] { "sus_a_1.png" }, new string[2] { "sus_b_1.png", "sus_b_2.png" }, new string[1] { "sus_c_1.png" } }, ["bruh"] = new string[3][] { new string[1] { "bruh_a_1.png" }, new string[2] { "bruh_b_1.png", "bruh_b_2.png" }, new string[1] { "bruh_c_1.png" } }, ["taxed"] = new string[3][] { new string[2] { "taxed_a_1.png", "taxed_a_2.png" }, new string[3] { "taxed_b_1.png", "taxed_b_2.png", "taxed_b_3.png" }, new string[1] { "taxed_c_1.png" } }, ["yoink"] = new string[3][] { new string[1] { "yoink_a_1.png" }, new string[4] { "yoink_b_1.png", "yoink_b_2.png", "yoink_b_3.png", "yoink_b_4.png" }, new string[1] { "yoink_c_1.png" } }, ["heavy"] = new string[3][] { new string[1] { "heavy_a_1.png" }, new string[3] { "heavy_b_1.png", "heavy_b_2.png", "heavy_b_3.png" }, new string[1] { "heavy_c_1.png" } }, ["tiny"] = new string[3][] { new string[1] { "tiny_a_1.png" }, new string[3] { "tiny_b_1.png", "tiny_b_2.png", "tiny_b_3.png" }, new string[1] { "tiny_c_1.png" } }, ["shh"] = new string[3][] { new string[1] { "shh_a_1.png" }, new string[3] { "shh_b_1.png", "shh_b_2.png", "shh_b_3.png" }, new string[1] { "shh_c_1.png" } } }; internal static readonly IReadOnlyDictionary<string, float> FrameRate = new Dictionary<string, float>(StringComparer.Ordinal) { ["loot"] = 9.6f, ["fire"] = 9.6f, ["oops"] = 5f, ["help"] = 3.33f, ["scared"] = 4.39f, ["run"] = 9.6f, ["rip"] = 4.8f, ["gg"] = 6.67f, ["clap"] = 1.67f, ["laugh"] = 4.55f, ["love"] = 2.5f, ["sleepy"] = 1f, ["question"] = 5f, ["mad"] = 4.55f, ["angry"] = 4f, ["duck"] = 10f, ["boo"] = 5f, ["aaaaa"] = 4.13f, ["yeeted"] = 5.45f, ["mybad"] = 4f, ["holdthis"] = 3.11f, ["bonk"] = 2.86f, ["teabag"] = 4.44f, ["sus"] = 2f, ["bruh"] = 3.45f, ["taxed"] = 12.5f, ["yoink"] = 4f, ["heavy"] = 3f, ["tiny"] = 3f, ["shh"] = 3f }; internal static readonly IReadOnlyList<string> MomentOnce = new string[12] { "oops", "help", "clap", "duck", "boo", "yeeted", "holdthis", "bonk", "sus", "taxed", "yoink", "shh" }; internal static readonly IReadOnlyDictionary<string, (int Phase, int Index)> Icon = new Dictionary<string, (int, int)>(StringComparer.Ordinal) { ["loot"] = (1, 2), ["fire"] = (1, 2), ["oops"] = (1, 1), ["help"] = (1, 1), ["scared"] = (1, 1), ["run"] = (1, 2), ["rip"] = (1, 1), ["gg"] = (1, 1), ["clap"] = (1, 1), ["laugh"] = (1, 1), ["love"] = (1, 1), ["sleepy"] = (1, 1), ["question"] = (1, 1), ["mad"] = (1, 1), ["angry"] = (1, 1), ["duck"] = (1, 1), ["boo"] = (1, 1), ["aaaaa"] = (1, 1), ["yeeted"] = (0, 2), ["mybad"] = (1, 1), ["holdthis"] = (1, 0), ["bonk"] = (1, 0), ["teabag"] = (1, 1), ["sus"] = (1, 1), ["bruh"] = (1, 1), ["taxed"] = (1, 1), ["yoink"] = (1, 2), ["heavy"] = (1, 1), ["tiny"] = (1, 1), ["shh"] = (1, 1) }; internal static readonly IReadOnlyDictionary<string, (int X0, int Y0, int X1, int Y1)> ArtBounds = new Dictionary<string, (int, int, int, int)>(StringComparer.Ordinal) { ["loot"] = (2, 5, 318, 315), ["fire"] = (59, 2, 260, 318), ["oops"] = (2, 36, 318, 284), ["help"] = (30, 2, 291, 318), ["scared"] = (7, 2, 313, 318), ["run"] = (2, 28, 318, 292), ["rip"] = (2, 16, 318, 304), ["gg"] = (62, 2, 258, 318), ["clap"] = (53, 2, 267, 318), ["laugh"] = (39, 2, 281, 318), ["love"] = (2, 7, 318, 313), ["sleepy"] = (45, 2, 275, 318), ["question"] = (66, 2, 253, 318), ["mad"] = (76, 2, 244, 318), ["angry"] = (78, 2, 242, 318), ["duck"] = (14, 2, 306, 318), ["boo"] = (2, 49, 318, 271), ["aaaaa"] = (60, 2, 260, 318), ["yeeted"] = (31, 2, 289, 318), ["mybad"] = (2, 53, 318, 267), ["holdthis"] = (2, 12, 318, 308), ["bonk"] = (2, 5, 318, 315), ["teabag"] = (78, 2, 242, 318), ["sus"] = (32, 2, 288, 318), ["bruh"] = (13, 2, 308, 318), ["taxed"] = (16, 2, 304, 318), ["yoink"] = (2, 32, 318, 287), ["heavy"] = (2, 59, 318, 261), ["tiny"] = (17, 2, 303, 318), ["shh"] = (2, 31, 318, 289) }; internal static readonly IReadOnlyList<string> Particles = new string[13] { "coin.png", "sparkle.png", "shard.png", "droplet.png", "dust.png", "wisp.png", "star.png", "steam.png", "z.png", "question.png", "ember.png", "confetti.png", "mote.png" }; } internal sealed class ReactionBubbleGraphic : MaskableGraphic { private readonly BubbleMesh mesh = new BubbleMesh(); private readonly BubbleTail tail = new BubbleTail(); private ReactionAnimCore.Extent body; private bool hasBody; private float aimX = float.NaN; private float aimY = float.NaN; private float aimScale = float.NaN; internal void Show(in ReactionAnimCore.Extent box, float headX, float headY, float scale, float alpha) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) bool flag = false; if (!hasBody || !Same(in body, in box)) { body = box; hasBody = true; aimX = (aimY = float.NaN); } if (ReactionBubbleLayout.NeedsAim(aimX, aimY, aimScale, headX, headY, scale)) { aimX = headX; aimY = headY; aimScale = scale; ReactionBubbleLayout.AimTail(body, headX, headY, scale, tail); flag = true; } if (flag) { ReactionBubbleLayout.Build(body, tail, mesh); ((Graphic)this).SetVerticesDirty(); } ((Graphic)this).color = new Color(1f, 1f, 1f, alpha); if (!((Behaviour)this).enabled) { ((Behaviour)this).enabled = true; } } internal void Hide() { if (((Behaviour)this).enabled) { ((Behaviour)this).enabled = false; } } private static bool Same(in ReactionAnimCore.Extent a, in ReactionAnimCore.Extent b) { if (a.Left == b.Left && a.Right == b.Right && a.Up == b.Up) { return a.Down == b.Down; } return false; } protected override void OnPopulateMesh(VertexHelper vh) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) vh.Clear(); Color color = ((Graphic)this).color; Color32 val = default(Color32); for (int i = 0; i < mesh.VertexCount; i++) { int num = mesh.Rgb[i]; ((Color32)(ref val))..ctor((byte)((float)((num >> 16) & 0xFF) * color.r + 0.5f), (byte)((float)((num >> 8) & 0xFF) * color.g + 0.5f), (byte)((float)(num & 0xFF) * color.b + 0.5f), (byte)(Mathf.Clamp01(mesh.Alpha[i] * color.a) * 255f + 0.5f)); vh.AddVert(new Vector3(mesh.X[i], mesh.Y[i], 0f), val, Vector4.zero); } for (int j = 0; j + 2 < mesh.IndexCount; j += 3) { vh.AddTriangle(mesh.Indices[j], mesh.Indices[j + 1], mesh.Indices[j + 2]); } } } internal static class ReactionBubbleLayout { internal readonly struct Bubble { internal readonly ReactionAnimCore.Extent Body; internal readonly ReactionAnimCore.Extent Reach; internal readonly float AnchorDx; internal Bubble(ReactionAnimCore.Extent body, ReactionAnimCore.Extent reach, float anchorDx) { Body = body; Reach = reach; AnchorDx = anchorDx; } } internal const float Pad = 12f; internal const float Radius = 36f; internal const float Keyline = 5f; internal const float ShadowDx = 5f; internal const float ShadowDy = -8f; internal const float ShadowSoftness = 12f; internal const float ShadowAlpha = 22f / 51f; internal const float EdgeFeather = 0.85f; internal const int FillRgb = 16777215; internal const float FillAlpha = 0.7f; internal const int InkRgb = 1972262; internal const int CornerSegments = 8; internal const int TipSegments = 8; private const float MitreSharp = 2f; private const float MitreDeep = 4f; internal const float HeadClear = 60f; internal const float HeadRadius = 33.625f; internal const float TailHalfBase = 17f; internal const float TailInset = 14f; internal const float TailGap = 6f; internal const float TailMax = 90f; internal const float TailBow = 6f; internal const float TailBowInner = 3f; internal const float TailBowMost = 6f; internal const int TailSteps = 12; internal const float AimTolerance = 0.5f; private const float MinScale = 0.01f; internal const int RimPoints = 36; private static readonly float[] CornerCos; private static readonly float[] CornerSin; private static readonly float[] BowAt; static ReactionBubbleLayout() { CornerCos = new float[36]; CornerSin = new float[36]; BowAt = new float[13]; float[] array = new float[4] { 180f, 270f, 0f, 90f }; int i = 0; int num = 0; for (; i < 4; i++) { int num2 = 0; while (num2 <= 8) { double num3 = ((double)array[i] + 90.0 * (double)num2 / 8.0) * Math.PI / 180.0; CornerCos[num] = (float)Math.Cos(num3); CornerSin[num] = (float)Math.Sin(num3); num2++; num++; } } for (int j = 1; j < 12; j++) { BowAt[j] = (float)Math.Sin(Math.PI * (double)j / 12.0); } } internal static bool TryArtExtent(string code, out ReactionAnimCore.Extent art) { art = default(ReactionAnimCore.Extent); if (code == null || !ReactionArt.ArtBounds.TryGetValue(code, out (int, int, int, int) value)) { return false; } float num = 0.875f; float num2 = 160f; art = new ReactionAnimCore.Extent((num2 - (float)value.Item1) * num, ((float)value.Item3 - num2) * num, (num2 - (float)value.Item2) * num, ((float)value.Item4 - num2) * num); return true; } internal static ReactionAnimCore.Extent ArtExtent(string code) { if (TryArtExtent(code, out var art)) { return art; } float num = 140f; return new ReactionAnimCore.Extent(num, num, num, num); } internal static ReactionAnimCore.Extent BodyOf(ReactionAnimCore.Script s, ReactionAnimCore.Extent art) { ReactionAnimCore.Frame frame = new ReactionAnimCore.Frame(); float num = 600f; float num2 = float.MinValue; float num3 = float.MinValue; float num4 = float.MinValue; float num5 = float.MinValue; int num6 = (int)Math.Round(0.35f * num); int num7 = (int)Math.Round(2.6f * num); for (int i = num6; i <= num7; i++) { ReactionAnimCore.EvaluateMotion(s, (float)i / num, frame); if (frame.Flight == 1f) { double num8 = (double)frame.Rotation * Math.PI / 180.0; double num9 = Math.Cos(num8); double num10 = Math.Sin(num8); for (int j = 0; j < 4; j++) { double num11 = (double)((j == 0 || j == 3) ? (0f - art.Left) : art.Right) * (double)frame.ScaleX; double num12 = (double)((j < 2) ? art.Up : (0f - art.Down)) * (double)frame.ScaleY; float num13 = (float)(num11 * num9 + num12 * num10) + frame.X; float num14 = (float)((0.0 - num11) * num10 + num12 * num9) + frame.Y; num2 = Math.Max(num2, 0f - num13); num3 = Math.Max(num3, num13); num4 = Math.Max(num4, num14); num5 = Math.Max(num5, 0f - num14); } } } if (num2 == float.MinValue) { num2 = art.Left; num3 = art.Right; num4 = art.Up; num5 = art.Down; } return new ReactionAnimCore.Extent(num2 + 12f, num3 + 12f, num4 + 12f, num5 + 12f); } internal static Bubble For(ReactionAnimCore.Script s) { return For(s, ArtExtent(s.Code)); } internal static Bubble For(ReactionAnimCore.Script s, ReactionAnimCore.Extent art) { ReactionAnimCore.Extent body = BodyOf(s, art); ReactionAnimCore.Extent extent = Drawn(body); ReactionAnimCore.Extent reach = s.Reach; return new Bubble(reach: new ReactionAnimCore.Extent(Math.Max(reach.Left, extent.Left), Math.Max(reach.Right, extent.Right), Math.Max(reach.Up, extent.Up), Math.Max(reach.Down, extent.Down)), body: body, anchorDx: Math.Max(190f, 60f + body.Left)); } internal static ReactionAnimCore.Extent Drawn(ReactionAnimCore.Extent body) { float num = 6f; return new ReactionAnimCore.Extent(body.Left + Math.Max(5f, 5f + num - 5f), body.Right + Math.Max(5f, 5f + num + 5f), body.Up + Math.Max(5f, 5f + num + -8f), body.Down + Math.Max(5f, 5f + num - -8f)); } internal static void Anchor(in Bubble b, float headX, float headY, float screenW, float screenH, out float ax, out float ay) { ReactionAnimCore.Anchor(b.Reach, b.AnchorDx, headX, headY, screenW, screenH, out ax, out ay); } internal static void Place(ReactionAnimCore.Frame f, float headX, float headY, float ax, float ay, out float x, out float y, out float scale) { ReactionAnimCore.FlightPosition(f, headX, headY, ax, ay, out x, out y); scale = f.FlightScale; } internal static void HeadInBubble(float headX, float headY, float x, float y, float scale, out float localX, out float localY) { float num = ((scale > 0.01f) ? scale : 0.01f); localX = (headX - x) / num; localY = (headY - y) / num; } internal static float TipToHead(float scale) { return 39.625f / ((scale > 0.01f) ? scale : 0.01f); } internal static bool NeedsAim(float lastX, float lastY, float lastScale, float x, float y, float scale) { float num = x - lastX; float num2 = y - lastY; float value = TipToHead(scale) - TipToHead(lastScale); if (num * num + num2 * num2 <= 0.25f && Math.Abs(value) <= 0.5f) { return float.IsNaN(lastScale); } return true; } internal static void AimTail(ReactionAnimCore.Extent body, float headX, float headY, float scale, BubbleTail tail) { tail.Visible = false; float num = 0f - body.Left; float right = body.Right; float num2 = 0f - body.Down; float up = body.Up; if (!(right > num) || !(up > num2) || float.IsNaN(headX) || float.IsNaN(headY) || float.IsInfinity(headX) || float.IsInfinity(headY)) { return; } float num3 = CornerRadius(body); float num4 = ((headX < num) ? (num - headX) : ((headX > right) ? (headX - right) : 0f)); float num5 = ((headY < num2) ? (num2 - headY) : ((headY > up) ? (headY - up) : 0f)); if (num4 <= 0f && num5 <= 0f) { return; } float num6 = 0f; float num7 = 0f; float num8; float num9; if (num4 >= num5) { num6 = ((headX < num) ? (-1f) : 1f); num8 = ((num6 < 0f) ? num : right) - num6 * 14f; num9 = Along(headY, num2 + num3 + 17f, up - num3 - 17f); } else { num7 = ((headY < num2) ? (-1f) : 1f); num9 = ((num7 < 0f) ? num2 : up) - num7 * 14f; num8 = Along(headX, num + num3 + 17f, right - num3 - 17f); } float num10 = headX - num8; float num11 = headY - num9; float num12 = (float)Math.Sqrt(num10 * num10 + num11 * num11); if (!(num12 > 0f)) { return; } float num13 = num10 / num12; float num14 = num11 / num12; float num15 = Math.Min(num12 - TipToHead(scale), 104f); if (num15 * (num13 * num6 + num14 * num7) - 14f > 1f) { float num16 = num8 + num13 * num15; float num17 = num9 + num14 * num15; float num18 = 0f - num14; float num19 = num13; if (num19 < 0f || (num19 == 0f && num18 > 0f)) { num18 = 0f - num18; num19 = 0f - num19; } float num20 = (0f - num7) * 17f; float num21 = num6 * 17f; if (num20 * num18 + num21 * num19 < 0f) { num20 = 0f - num20; num21 = 0f - num21; } float num22 = num8 + num20; float num23 = num9 + num21; float num24 = num8 - num20; float num25 = num9 - num21; for (int i = 0; i <= 12; i++) { float num26 = (float)i / 12f; float num27 = BowAt[i]; tail.UpperX[i] = num22 + (num16 - num22) * num26 + num18 * 6f * num27; tail.UpperY[i] = num23 + (num17 - num23) * num26 + num19 * 6f * num27; tail.LowerX[i] = num24 + (num16 - num24) * num26 + num18 * 3f * num27;