Decompiled source of TokControl REPO Tiktoklive v1.2.2

BepInEx\plugins\TokControl_REPO_Tiktoklive.dll

Decompiled 9 hours ago
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<int> _portConfig;

		private ConfigEntry<bool> _logToUnityConfig;

		private ConfigEntry<string> _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<MainThreadDispatcher>();
			_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<int>("Server", "Port", 8080, "Local WebSocket port for TokControl commands (ws://127.0.0.1:PORT/)");
			_logToUnityConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogToUnityConsole", true, "Mirror TokControl bridge logs to Unity debug console");
			_defaultGhostEnemyConfig = ((BaseUnityPlugin)this).Config.Bind<string>("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<Action> Queue = new ConcurrentQueue<Action>();

		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<EventData>)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<SpawnRelayPayload>(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<byte>(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<byte>(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<CommandResult> 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<PrefabRef>)Valuables.AllValuables).FirstOrDefault((Func<PrefabRef, bool>)((PrefabRef v) => v != null && (string.Equals(((PrefabRef<GameObject>)(object)v).PrefabName, valuableName, StringComparison.OrdinalIgnoreCase) || (((PrefabRef<GameObject>)(object)v).PrefabName ?? "").IndexOf(valuableName, StringComparison.OrdinalIgnoreCase) >= 0)));
				if (val == null || !((PrefabRef<GameObject>)(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<GameObject>)(object)val).PrefabName}' x{count} for @{user}");
				return CommandResult.Ok("spawned_valuable:" + ((PrefabRef<GameObject>)(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<Item>)Items.AllItems).FirstOrDefault((Func<Item, bool>)((Item i) => (Object)(object)i != (Object)null && string.Equals(i.itemName, name, StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			return ((IEnumerable<Item>)Items.AllItems).FirstOrDefault((Func<Item, bool>)((Item i) => (Object)(object)i != (Object)null && i.itemName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0));
		}

		private static IEnumerable<string> 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<string> 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<string, string> EnemyAliases = new Dictionary<string, string>(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);
		}
	}
}