using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using Microsoft.CodeAnalysis; using REPOLib.Modules; using TokControlREPOBridge.Commands; using TokControlREPOBridge.Logging; using TokControlREPOBridge.Network; using TokControlREPOBridge.Util; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TokControl_REPO_Tiktoklive")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("TokControl WebSocket bridge for R.E.P.O. TikTok Live — spawn items and enemies from stream gifts")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+1d47cf9520f4d8d4af7f2d50ca10714c95d509f0")] [assembly: AssemblyProduct("TokControl_REPO_Tiktoklive")] [assembly: AssemblyTitle("TokControl_REPO_Tiktoklive")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.0.0")] [module: UnverifiableCode] 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; } } internal static class IsExternalInit { } } namespace TokControlREPOBridge { [BepInPlugin("com.tokcontrol.repobridge", "TokControl_REPO_Tiktoklive", "1.2.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private ConfigEntry _portConfig; private ConfigEntry _logToUnityConfig; private ConfigEntry _defaultGhostEnemyConfig; private WebSocketServer? _server; private CommandProcessor? _processor; private GameObject? _dispatcherObject; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); ModLog.Info("=== TokControl_REPO_Tiktoklive v1.2.2 ==="); ModLog.Info("REPOLib dependency OK — initializing WebSocket bridge"); _dispatcherObject = new GameObject("TokControl_MainThreadDispatcher"); Object.DontDestroyOnLoad((Object)(object)_dispatcherObject); _dispatcherObject.AddComponent(); _processor = new CommandProcessor(_defaultGhostEnemyConfig.Value); SpawnRelay.Initialize(_processor.Actions); _server = new WebSocketServer(_portConfig.Value, _processor); _server.Start(); ModLog.Info($"WebSocket listening on ws://127.0.0.1:{_portConfig.Value}/"); ModLog.Info("Waiting for TokControl / Pandy App commands..."); } private void BindConfig() { _portConfig = ((BaseUnityPlugin)this).Config.Bind("Server", "Port", 8080, "Local WebSocket port for TokControl commands (ws://127.0.0.1:PORT/)"); _logToUnityConfig = ((BaseUnityPlugin)this).Config.Bind("Debug", "LogToUnityConsole", true, "Mirror TokControl bridge logs to Unity debug console"); _defaultGhostEnemyConfig = ((BaseUnityPlugin)this).Config.Bind("Gameplay", "DefaultGhostEnemy", "Hidden", "Enemy name used for spawn_ghost when no name is provided (e.g. Hidden, Robe, Hunter)"); } internal static bool ShouldLogToUnity() { return Instance?._logToUnityConfig?.Value ?? true; } private void OnDestroy() { _server?.Dispose(); if ((Object)(object)_dispatcherObject != (Object)null) { Object.Destroy((Object)(object)_dispatcherObject); } ModLog.Info("TokControl_REPO_Tiktoklive shut down"); } } public static class PluginInfo { public const string PLUGIN_GUID = "com.tokcontrol.repobridge"; public const string PLUGIN_NAME = "TokControl_REPO_Tiktoklive"; public const string PLUGIN_VERSION = "1.2.2"; } } namespace TokControlREPOBridge.Util { public class MainThreadDispatcher : MonoBehaviour { private static readonly ConcurrentQueue Queue = new ConcurrentQueue(); public static bool IsReady { get; private set; } public static void Enqueue(Action action) { if (action != null) { Queue.Enqueue(action); } } private void Awake() { IsReady = true; } private void OnDestroy() { IsReady = false; } private void Update() { Action result; while (Queue.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ModLog.Error("MainThread action failed: " + ex.Message); } } } } } namespace TokControlREPOBridge.Network { public static class SpawnRelay { private sealed class SpawnRelayPayload { public string Cmd { get; set; } = ""; public string Name { get; set; } = ""; public int Count { get; set; } = 1; public string User { get; set; } = "viewer"; } private const string EventName = "TokControl_SpawnRelay_v1"; private static NetworkedEvent? _relayEvent; private static GameActions? _actions; public static void Initialize(GameActions actions) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown _actions = actions; _relayEvent = new NetworkedEvent("TokControl_SpawnRelay_v1", (Action)OnRelayReceived); ModLog.Info("Spawn relay initialized (client → host)"); } public static CommandResult ExecuteSpawn(string cmd, string name, int count, string user) { if (_actions == null) { return CommandResult.Fail("relay_not_ready"); } if (!MainThreadDispatcher.IsReady) { return CommandResult.Fail("game_not_ready"); } if (SemiFunc.IsMasterClientOrSingleplayer()) { return ExecuteLocally(cmd, name, count, user); } if (!SemiFunc.IsMultiplayer()) { return ExecuteLocally(cmd, name, count, user); } return RelayToHost(cmd, name, count, user); } private static CommandResult RelayToHost(string cmd, string name, int count, string user) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (_relayEvent == null) { return CommandResult.Fail("relay_not_ready"); } try { string text = JsonSerializer.Serialize(new SpawnRelayPayload { Cmd = cmd, Name = name, Count = count, User = user }); _relayEvent.RaiseEvent((object)text, NetworkingEvents.RaiseMasterClient, SendOptions.SendReliable); ModLog.Info($"Relayed to host: {cmd} {name} x{count} for @{user}"); return CommandResult.Ok("relayed_to_host", "Spawn sent to lobby host — host must have this mod installed"); } catch (Exception ex) { ModLog.Error("Relay failed: " + ex.Message); return CommandResult.Fail("relay_failed:" + ex.Message); } } private static void OnRelayReceived(EventData eventData) { if (_actions == null) { return; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { ModLog.Debug("Relay received on non-host — ignored"); return; } try { string text = eventData.CustomData as string; if (string.IsNullOrWhiteSpace(text)) { ModLog.Warn("Relay payload empty"); return; } SpawnRelayPayload payload = JsonSerializer.Deserialize(text); if (payload == null || string.IsNullOrWhiteSpace(payload.Cmd)) { ModLog.Warn("Relay payload invalid"); return; } ModLog.Info($"Host executing relay: {payload.Cmd} {payload.Name} x{payload.Count} for @{payload.User}"); MainThreadDispatcher.Enqueue(delegate { ExecuteLocally(payload.Cmd, payload.Name, payload.Count, payload.User ?? "viewer"); }); } catch (Exception ex) { ModLog.Error("Relay handler error: " + ex.Message); } } private static CommandResult ExecuteLocally(string cmd, string name, int count, string user) { if (_actions == null) { return CommandResult.Fail("actions_not_ready"); } count = Math.Max(1, Math.Min(count, 50)); cmd = cmd.Trim().ToLowerInvariant(); switch (cmd) { case "spawn_item": case "spawnitem": case "item": return _actions.SpawnItemLocal(name, count, user); case "spawnghost": case "spawn_ghost": case "ghost": return _actions.SpawnEnemyLocal(name, count, user); case "spawnenemy": case "spawn_enemy": case "enemy": return _actions.SpawnEnemyLocal(name, count, user); case "spawn_valuable": case "spawnvaluable": case "valuable": return _actions.SpawnValuableLocal(name, count, user); default: return CommandResult.Fail("unknown_spawn_cmd:" + cmd); } } } public sealed class WebSocketServer : IDisposable { private readonly int _port; private readonly CommandProcessor _processor; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private HttpListener? _listener; private Task? _acceptTask; public WebSocketServer(int port, CommandProcessor processor) { _port = port; _processor = processor; } public void Start() { _listener = new HttpListener(); _listener.Prefixes.Add($"http://127.0.0.1:{_port}/"); _listener.Prefixes.Add($"http://localhost:{_port}/"); _listener.Start(); _acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token)); ModLog.Info($"HTTP/WebSocket listener started on port {_port}"); } private async Task AcceptLoopAsync(CancellationToken ct) { while (!ct.IsCancellationRequested && _listener != null && _listener.IsListening) { HttpListenerContext context = null; try { context = await _listener.GetContextAsync().ConfigureAwait(continueOnCapturedContext: false); } catch (HttpListenerException) when (ct.IsCancellationRequested) { break; } catch (ObjectDisposedException) { break; } catch (Exception ex3) { ModLog.Warn("Accept error: " + ex3.Message); continue; } Task.Run(() => HandleContextAsync(context, ct), ct); } } private async Task HandleContextAsync(HttpListenerContext context, CancellationToken ct) { _ = 3; try { if (context.Request.IsWebSocketRequest) { await HandleWebSocketAsync((await context.AcceptWebSocketAsync(null).ConfigureAwait(continueOnCapturedContext: false)).WebSocket, ct).ConfigureAwait(continueOnCapturedContext: false); return; } string text = context.Request.Url?.AbsolutePath ?? "/"; string s; if (text.Equals("/health", StringComparison.OrdinalIgnoreCase)) { s = "{\"ok\":true,\"mod\":\"TokControl_REPO_Tiktoklive\"}"; } else if (context.Request.HttpMethod == "POST") { using StreamReader reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding); string raw = await reader.ReadToEndAsync().ConfigureAwait(continueOnCapturedContext: false); CommandResult commandResult = _processor.Process(raw); s = commandResult.ToJson(); } else { s = "{\"ok\":true,\"hint\":\"Connect via WebSocket ws://127.0.0.1:" + _port + "/\"}"; } byte[] bytes = Encoding.UTF8.GetBytes(s); context.Response.StatusCode = 200; context.Response.ContentType = "application/json"; context.Response.ContentLength64 = bytes.Length; await context.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(continueOnCapturedContext: false); context.Response.Close(); } catch (Exception ex) { ModLog.Warn("Request handler error: " + ex.Message); try { context.Response.StatusCode = 500; context.Response.Close(); } catch { } } } private async Task HandleWebSocketAsync(WebSocket socket, CancellationToken ct) { byte[] buffer = new byte[8192]; ModLog.Info("WebSocket client connected"); try { _ = 1; try { while (socket.State == WebSocketState.Open && !ct.IsCancellationRequested) { WebSocketReceiveResult webSocketReceiveResult = await socket.ReceiveAsync(new ArraySegment(buffer), ct).ConfigureAwait(continueOnCapturedContext: false); if (webSocketReceiveResult.MessageType != WebSocketMessageType.Close) { if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text) { string text = Encoding.UTF8.GetString(buffer, 0, webSocketReceiveResult.Count); ModLog.Debug("WS recv: " + text); CommandResult commandResult = _processor.Process(text); byte[] bytes = Encoding.UTF8.GetBytes(commandResult.ToJson()); await socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, endOfMessage: true, ct).ConfigureAwait(continueOnCapturedContext: false); } continue; } break; } } catch (WebSocketException ex) { ModLog.Debug("WebSocket closed: " + ex.Message); } catch (Exception ex2) { ModLog.Warn("WebSocket error: " + ex2.Message); } } finally { ModLog.Info("WebSocket client disconnected"); try { if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived) { await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); } } catch { } socket.Dispose(); } } public void Dispose() { _cts.Cancel(); try { _listener?.Stop(); } catch { } try { _listener?.Close(); } catch { } _listener = null; _cts.Dispose(); } } } namespace TokControlREPOBridge.Logging { public static class ModLog { public static void Info(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)Format(message)); } if (Plugin.ShouldLogToUnity()) { Debug.Log((object)Format(message)); } } public static void Warn(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)Format(message)); } if (Plugin.ShouldLogToUnity()) { Debug.LogWarning((object)Format(message)); } } public static void Error(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)Format(message)); } if (Plugin.ShouldLogToUnity()) { Debug.LogError((object)Format(message)); } } public static void Debug(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)Format(message)); } } private static string Format(string message) { return "[TokControl] " + message; } } } namespace TokControlREPOBridge.Commands { public sealed class CommandProcessor { private readonly string _defaultGhostEnemy; private readonly GameActions _actions = new GameActions(); internal GameActions Actions => _actions; public CommandProcessor(string defaultGhostEnemy) { _defaultGhostEnemy = (string.IsNullOrWhiteSpace(defaultGhostEnemy) ? "Hidden" : defaultGhostEnemy.Trim()); } public CommandResult Process(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return CommandResult.Fail("empty_message"); } raw = raw.Trim(); ModLog.Info("Command received: " + raw); try { if (raw.StartsWith("{", StringComparison.Ordinal)) { return ProcessJson(raw); } return ProcessPlainText(raw); } catch (Exception ex) { ModLog.Error("Command parse error: " + ex.Message); return CommandResult.Fail(ex.Message); } } private CommandResult ProcessJson(string json) { using JsonDocument jsonDocument = JsonDocument.Parse(json); JsonElement rootElement = jsonDocument.RootElement; string text = GetString(rootElement, "cmd") ?? GetString(rootElement, "command") ?? GetString(rootElement, "action") ?? ""; text = text.Trim().ToLowerInvariant(); string text2 = GetString(rootElement, "eventId") ?? GetString(rootElement, "event"); string name = GetString(rootElement, "name") ?? GetString(rootElement, "item") ?? GetString(rootElement, "enemy") ?? GetString(rootElement, "gift") ?? ""; int count = GetInt(rootElement, "count") ?? GetInt(rootElement, "amount") ?? 1; string user = GetString(rootElement, "user") ?? GetString(rootElement, "uniqueId") ?? "viewer"; if (string.IsNullOrEmpty(text) && rootElement.TryGetProperty("command", out var value)) { return ProcessPlainText(value.GetString() ?? ""); } if (!string.IsNullOrEmpty(text2) && (string.IsNullOrEmpty(text) || text == "event")) { return Dispatch(text2.Trim().ToLowerInvariant(), "", count, user); } return Dispatch(text, name, count, user); } private CommandResult ProcessPlainText(string text) { string[] array = text.Split('|'); if (array.Length >= 2) { string cmd = array[0].Trim().ToLowerInvariant(); string name = ((array.Length > 1) ? array[1].Trim() : ""); int result; int count = ((array.Length <= 2 || !int.TryParse(array[2], out result)) ? 1 : result); return Dispatch(cmd, name, count, "viewer"); } string[] array2 = text.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (array2.Length == 0) { return CommandResult.Fail("empty_command"); } string cmd2 = array2[0].ToLowerInvariant(); string name2 = ((array2.Length > 1) ? array2[1] : ""); int result2; int count2 = ((array2.Length <= 2 || !int.TryParse(array2[2], out result2)) ? 1 : result2); return Dispatch(cmd2, name2, count2, "viewer"); } private CommandResult Dispatch(string cmd, string name, int count, string user) { count = Math.Max(1, Math.Min(count, 50)); switch (cmd) { case "ping": case "health": return CommandResult.Ok("pong", "Server alive"); case "item": case "spawn_item": case "spawnitem": if (string.IsNullOrWhiteSpace(name)) { return CommandResult.Fail("spawn_item requires a name"); } return RunOnMainThread(() => SpawnRelay.ExecuteSpawn(cmd, name, count, user)); case "spawnghost": case "spawn_ghost": case "ghost": { string enemy = (string.IsNullOrWhiteSpace(name) ? _defaultGhostEnemy : name); return RunOnMainThread(() => SpawnRelay.ExecuteSpawn(cmd, enemy, count, user)); } case "spawnenemy": case "spawn_enemy": case "enemy": if (string.IsNullOrWhiteSpace(name)) { return CommandResult.Fail("spawn_enemy requires a name"); } return RunOnMainThread(() => SpawnRelay.ExecuteSpawn(cmd, name, count, user)); case "spawn_valuable": case "spawnvaluable": case "valuable": if (string.IsNullOrWhiteSpace(name)) { return CommandResult.Fail("spawn_valuable requires a name"); } return RunOnMainThread(() => SpawnRelay.ExecuteSpawn(cmd, name, count, user)); case "list_items": return RunOnMainThread(() => _actions.ListItems()); case "list_enemies": return RunOnMainThread(() => _actions.ListEnemies()); default: { if (RepoEventResolver.TryResolve(cmd, out string eventCmd, out string eventTarget)) { ModLog.Info("REPO event '" + cmd + "' -> " + eventCmd + " '" + eventTarget + "'"); return RunOnMainThread(() => SpawnRelay.ExecuteSpawn(eventCmd, eventTarget, count, user)); } ModLog.Warn("Unknown command: " + cmd); return CommandResult.Fail("unknown_command:" + cmd); } } } private static CommandResult RunOnMainThread(Func action) { if (!MainThreadDispatcher.IsReady) { return CommandResult.Fail("game_not_ready"); } CommandResult result = null; ManualResetEventSlim wait = new ManualResetEventSlim(initialState: false); MainThreadDispatcher.Enqueue(delegate { try { result = action(); } catch (Exception ex) { result = CommandResult.Fail(ex.Message); } finally { wait.Set(); } }); if (!wait.Wait(TimeSpan.FromSeconds(10.0))) { return CommandResult.Fail("main_thread_timeout"); } return result ?? CommandResult.Fail("no_result"); } private static string? GetString(JsonElement el, string prop) { if (!el.TryGetProperty(prop, out var value)) { return null; } if (value.ValueKind != JsonValueKind.String) { return value.ToString(); } return value.GetString(); } private static int? GetInt(JsonElement el, string prop) { if (!el.TryGetProperty(prop, out var value)) { return null; } if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var value2)) { return value2; } if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), out var result)) { return result; } return null; } } public sealed class CommandResult { public bool Success { get; init; } public string Message { get; init; } = ""; public string? Detail { get; init; } public static CommandResult Ok(string message, string? detail = null) { return new CommandResult { Success = true, Message = message, Detail = detail }; } public static CommandResult Fail(string message) { return new CommandResult { Success = false, Message = message }; } public string ToJson() { return JsonSerializer.Serialize(new { success = Success, message = Message, detail = Detail }); } } public sealed class GameActions { internal CommandResult SpawnItemLocal(string itemName, int count, string user) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) Item val = FindItem(itemName); if ((Object)(object)val == (Object)null) { ModLog.Warn("Item not found: " + itemName); return CommandResult.Fail("item_not_found:" + itemName); } int num = 0; for (int i = 0; i < count; i++) { Vector3 positionInFrontOfPlayer = SpawnHelper.GetPositionInFrontOfPlayer((float)i * 0.4f); Quaternion identity = Quaternion.identity; GameObject val2 = Items.SpawnItem(val, positionInFrontOfPlayer, identity); if ((Object)(object)val2 != (Object)null) { num++; } } ModLog.Info($"spawn_item '{val.itemName}' x{count} for @{user} (spawned {num})"); return CommandResult.Ok("spawned_item:" + val.itemName, $"count={num}"); } internal CommandResult SpawnEnemyLocal(string enemyName, int count, string user) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) EnemySetup val = FindEnemy(enemyName); if ((Object)(object)val == (Object)null) { ModLog.Warn("Enemy not found: " + enemyName); return CommandResult.Fail("enemy_not_found:" + enemyName); } string text = ((Object)val).name ?? enemyName; for (int i = 0; i < count; i++) { Vector3 positionInFrontOfPlayer = SpawnHelper.GetPositionInFrontOfPlayer(1.5f + (float)i * 0.8f); Enemies.SpawnEnemy(val, positionInFrontOfPlayer, Quaternion.identity, false); } ModLog.Info($"spawn_enemy '{text}' x{count} for @{user}"); return CommandResult.Ok("spawned_enemy:" + text, $"count={count}"); } internal CommandResult SpawnValuableLocal(string valuableName, int count, string user) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) try { PrefabRef val = ((IEnumerable)Valuables.AllValuables).FirstOrDefault((Func)((PrefabRef v) => v != null && (string.Equals(((PrefabRef)(object)v).PrefabName, valuableName, StringComparison.OrdinalIgnoreCase) || (((PrefabRef)(object)v).PrefabName ?? "").IndexOf(valuableName, StringComparison.OrdinalIgnoreCase) >= 0))); if (val == null || !((PrefabRef)(object)val).IsValid()) { return CommandResult.Fail("valuable_not_found:" + valuableName); } for (int num = 0; num < count; num++) { Vector3 positionInFrontOfPlayer = SpawnHelper.GetPositionInFrontOfPlayer((float)num * 0.5f); Valuables.SpawnValuable(val, positionInFrontOfPlayer, Quaternion.identity); } ModLog.Info($"spawn_valuable '{((PrefabRef)(object)val).PrefabName}' x{count} for @{user}"); return CommandResult.Ok("spawned_valuable:" + ((PrefabRef)(object)val).PrefabName); } catch (Exception ex) { ModLog.Error("spawn_valuable error: " + ex.Message); return CommandResult.Fail(ex.Message); } } public CommandResult ListItems() { StringBuilder stringBuilder = new StringBuilder(); foreach (Item item in Items.AllItems.Take(80)) { if (!((Object)(object)item == (Object)null)) { stringBuilder.AppendLine(item.itemName); } } string text = stringBuilder.ToString(); ModLog.Info($"list_items ({Items.AllItems.Count} total)\n{text}"); return CommandResult.Ok("list_items", text); } public CommandResult ListEnemies() { StringBuilder stringBuilder = new StringBuilder(); foreach (EnemySetup item in Enemies.AllEnemies.Take(80)) { if (!((Object)(object)item == (Object)null)) { stringBuilder.AppendLine(((Object)item).name); } } string text = stringBuilder.ToString(); ModLog.Info($"list_enemies ({Enemies.AllEnemies.Count} total)\n{text}"); return CommandResult.Ok("list_enemies", text); } private static Item? FindItem(string name) { if (string.IsNullOrWhiteSpace(name)) { return null; } name = name.Trim(); foreach (string item in ExpandItemSearchTerms(name)) { Item val = FindItemSingle(item); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Item? FindItemSingle(string name) { Item val = ((IEnumerable)Items.AllItems).FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && string.Equals(i.itemName, name, StringComparison.OrdinalIgnoreCase))); if ((Object)(object)val != (Object)null) { return val; } return ((IEnumerable)Items.AllItems).FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && i.itemName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)); } private static IEnumerable ExpandItemSearchTerms(string name) { yield return name; string lower = name.ToLowerInvariant(); bool flag; switch (lower) { case "gun": case "handgun": case "pistol": flag = true; break; default: flag = false; break; } if (flag) { yield return "gun"; yield return "handgun"; yield return "pistol"; } if (lower.Contains("health") || lower.Contains("medkit") || lower.Contains("med")) { yield return "medkit"; yield return "health"; } if (lower.Contains("shotgun")) { yield return "shotgun"; } if (lower.Contains("grenade") || lower == "expl") { yield return "grenade"; } if (lower.Contains("stun")) { yield return "stun"; } if (lower.Contains("shock")) { yield return "shock"; } if (lower.Contains("duck")) { yield return "duck"; } if (lower.Contains("flashlight")) { yield return "flashlight"; } if (lower.Contains("bat")) { yield return "bat"; } } private static EnemySetup? FindEnemy(string name) { if (string.IsNullOrWhiteSpace(name)) { return null; } name = name.Trim(); foreach (string item in ExpandEnemySearchTerms(name)) { foreach (EnemySetup allEnemy in Enemies.AllEnemies) { if (!((Object)(object)allEnemy == (Object)null)) { string text = ((Object)allEnemy).name ?? ""; if (string.Equals(text, item, StringComparison.OrdinalIgnoreCase)) { return allEnemy; } if (text.IndexOf(item, StringComparison.OrdinalIgnoreCase) >= 0) { return allEnemy; } } } } return null; } private static IEnumerable ExpandEnemySearchTerms(string name) { yield return name; string lower = name.Replace(" ", "").ToLowerInvariant(); if (lower.Contains("huntsman") || lower.Contains("hunter")) { yield return "Hunter"; yield return "Huntsman"; } if (lower.Contains("thinman") || lower.Contains("thin")) { yield return "Thin Man"; } if (lower.Contains("hidden")) { yield return "Hidden"; } if (lower.Contains("duck")) { yield return "Duck"; } if (lower.Contains("spewer")) { yield return "Spewer"; } } } internal static class SpawnHelper { public static Vector3 GetPositionInFrontOfPlayer(float forwardOffset = 2f) { //IL_0019: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Camera.main != (Object)null) { Transform transform = ((Component)Camera.main).transform; return transform.position + transform.forward * forwardOffset + Vector3.up * 0.25f; } GameObject val = GameObject.FindGameObjectWithTag("Player"); if ((Object)(object)val != (Object)null) { return val.transform.position + val.transform.forward * forwardOffset + Vector3.up * 0.25f; } return Vector3.up * 2f; } } public static class RepoEventResolver { private static readonly TextInfo TextInfo = CultureInfo.InvariantCulture.TextInfo; private static readonly Dictionary EnemyAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["huntsman"] = "Hunter", ["thinman"] = "Thin Man", ["birthday_boy"] = "Birthday Boy", ["upscream"] = "Upscream", ["headgrab"] = "Head", ["cleanup_crew"] = "Valuable Thrower", ["heart_hugger"] = "Floater", ["clown"] = "Birthday Boy", ["baby"] = "Birthday Boy", ["alien"] = "Beamer", ["frog"] = "Animal", ["dogo"] = "Animal", ["bella"] = "Hidden", ["oogly"] = "Slow Mouth", ["loom"] = "Robe", ["trudge"] = "Slow Walker", ["reaper"] = "Robe", ["ceiling_eye"] = "Ceiling Eye", ["slow_walker"] = "Slow Walker", ["slow_mouth"] = "Slow Mouth", ["valuable_thrower"] = "Valuable Thrower" }; public static bool TryResolve(string eventId, out string spawnCmd, out string targetName) { spawnCmd = ""; targetName = ""; if (string.IsNullOrWhiteSpace(eventId)) { return false; } eventId = eventId.Trim().ToLowerInvariant(); if (eventId.StartsWith("spawn_", StringComparison.Ordinal)) { spawnCmd = "spawn_ghost"; string text = eventId.Substring(6); targetName = (EnemyAliases.TryGetValue(text, out string value) ? value : TitleizeSlug(text)); return true; } if (eventId.StartsWith("item_", StringComparison.Ordinal)) { spawnCmd = "spawn_item"; targetName = ItemSearchFromSlug(eventId.Substring(5)); return true; } if (eventId.StartsWith("loot_", StringComparison.Ordinal)) { spawnCmd = "spawn_valuable"; targetName = LootSearchFromId(eventId); return true; } if (eventId.StartsWith("active_nade_", StringComparison.Ordinal)) { spawnCmd = "spawn_item"; string text2 = eventId.Substring("active_nade_".Length); targetName = text2 switch { "stun" => "stun", "shock" => "shock", "expl" => "grenade", "duck" => "duck", _ => text2, }; return true; } return false; } private static string ItemSearchFromSlug(string slug) { slug = slug.Replace('_', ' ').Trim(); if (slug.StartsWith("health", StringComparison.OrdinalIgnoreCase)) { return "health"; } if (slug.StartsWith("nade", StringComparison.OrdinalIgnoreCase)) { return slug; } if (string.Equals(slug, "handgun", StringComparison.OrdinalIgnoreCase)) { return "gun"; } if (string.Equals(slug, "health big", StringComparison.OrdinalIgnoreCase) || string.Equals(slug, "health med", StringComparison.OrdinalIgnoreCase) || string.Equals(slug, "health small", StringComparison.OrdinalIgnoreCase)) { return "medkit"; } return slug; } private static string LootSearchFromId(string eventId) { if (eventId.Contains("rand", StringComparison.OrdinalIgnoreCase)) { return "Gold"; } string slug = (eventId.StartsWith("loot_", StringComparison.Ordinal) ? eventId.Substring(5) : eventId); return TitleizeSlug(slug); } private static string TitleizeSlug(string slug) { string[] array = slug.Split(new char[2] { '_', ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { if (array[i].Length > 0) { array[i] = TextInfo.ToTitleCase(array[i]); } } return string.Join(" ", array); } } }