Decompiled source of TokControl REPO Bridge v1.1.0

BepInEx/plugins/TokControlREPOBridge.dll

Decompiled 9 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
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("TokControlREPOBridge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("TokControl WebSocket bridge for R.E.P.O. — spawn items and enemies from stream gifts")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+1d47cf9520f4d8d4af7f2d50ca10714c95d509f0")]
[assembly: AssemblyProduct("TokControlREPOBridge")]
[assembly: AssemblyTitle("TokControlREPOBridge")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.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 Bridge", "1.1.0")]
	[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 Bridge v1.1.0 ===");
			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 Bridge shut down");
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.tokcontrol.repobridge";

		public const string PLUGIN_NAME = "TokControl REPO Bridge";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
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, Chaos Tricks style)");
		}

		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\":\"TokControlREPOBridge\"}";
				}
				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 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() ?? "");
			}
			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:
				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();
			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 EnemySetup? FindEnemy(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			name = name.Trim();
			foreach (EnemySetup allEnemy in Enemies.AllEnemies)
			{
				if (!((Object)(object)allEnemy == (Object)null))
				{
					string text = ((Object)allEnemy).name ?? "";
					if (string.Equals(text, name, StringComparison.OrdinalIgnoreCase))
					{
						return allEnemy;
					}
					if (text.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return allEnemy;
					}
				}
			}
			return null;
		}
	}
	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;
		}
	}
}