mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
1341 lines
No EOL
52 KiB
C#
1341 lines
No EOL
52 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Net.WebSockets;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MinecraftClient.CommandHandler;
|
|
using MinecraftClient.Inventory;
|
|
using MinecraftClient.Mapping;
|
|
using MinecraftClient.Scripting;
|
|
using Newtonsoft.Json;
|
|
using Tomlet.Attributes;
|
|
|
|
namespace MinecraftClient.ChatBots;
|
|
|
|
internal class SessionEventArgs : EventArgs
|
|
{
|
|
public string SessionId { get; }
|
|
|
|
public SessionEventArgs(string sessionId)
|
|
{
|
|
SessionId = sessionId;
|
|
}
|
|
}
|
|
|
|
internal class MessageReceivedEventArgs : EventArgs
|
|
{
|
|
public string SessionId { get; }
|
|
public string Message { get; }
|
|
|
|
public MessageReceivedEventArgs(string sessionId, string message)
|
|
{
|
|
SessionId = sessionId;
|
|
Message = message;
|
|
}
|
|
}
|
|
|
|
internal class WebSocketSession
|
|
{
|
|
public string SessionId { get; set; }
|
|
public WebSocket WebSocket { get; set; }
|
|
|
|
public WebSocketSession(string sessionId, WebSocket webSocket)
|
|
{
|
|
SessionId = sessionId;
|
|
WebSocket = webSocket;
|
|
}
|
|
}
|
|
|
|
internal class WebSocketServer
|
|
{
|
|
public readonly ConcurrentDictionary<string, WebSocketSession> Sessions;
|
|
public event EventHandler<SessionEventArgs>? NewSession;
|
|
public event EventHandler<SessionEventArgs>? SessionDropped;
|
|
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
|
|
|
|
private HttpListener? listener;
|
|
|
|
public WebSocketServer()
|
|
{
|
|
Sessions = new ConcurrentDictionary<string, WebSocketSession>();
|
|
}
|
|
|
|
public async Task Start(string ipAddress, int port)
|
|
{
|
|
listener = new HttpListener();
|
|
listener.Prefixes.Add($"http://{ipAddress}:{port}/");
|
|
listener.Start();
|
|
|
|
while (listener.IsListening)
|
|
{
|
|
var context = await listener.GetContextAsync();
|
|
if (context.Request.IsWebSocketRequest)
|
|
{
|
|
var sessionGuid = Guid.NewGuid().ToString();
|
|
var webSocketContext = await context.AcceptWebSocketAsync(null);
|
|
var webSocket = webSocketContext.WebSocket;
|
|
var webSocketSession = new WebSocketSession(sessionGuid, webSocket);
|
|
|
|
NewSession?.Invoke(this, new SessionEventArgs(sessionGuid));
|
|
Sessions.TryAdd(sessionGuid, webSocketSession);
|
|
_ = ProcessWebSocketSession(webSocketSession);
|
|
}
|
|
else
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
context.Response.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task Stop()
|
|
{
|
|
foreach (var session in Sessions)
|
|
{
|
|
await session.Value.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server shutting down",
|
|
CancellationToken.None);
|
|
}
|
|
|
|
Sessions.Clear();
|
|
listener?.Stop();
|
|
}
|
|
|
|
private async Task ProcessWebSocketSession(WebSocketSession webSocketSession)
|
|
{
|
|
var buffer = new byte[1024];
|
|
|
|
try
|
|
{
|
|
while (webSocketSession.WebSocket.State == WebSocketState.Open)
|
|
{
|
|
var receiveResult =
|
|
await webSocketSession.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer),
|
|
CancellationToken.None);
|
|
|
|
if (receiveResult.MessageType == WebSocketMessageType.Text)
|
|
{
|
|
var message = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
|
|
MessageReceived?.Invoke(this, new MessageReceivedEventArgs(webSocketSession.SessionId, message));
|
|
}
|
|
else if (receiveResult.MessageType == WebSocketMessageType.Close)
|
|
{
|
|
await webSocketSession.WebSocket.CloseAsync(
|
|
WebSocketCloseStatus.NormalClosure,
|
|
"Connection closed by the client",
|
|
CancellationToken.None);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Sessions.TryRemove(webSocketSession.SessionId, out _);
|
|
SessionDropped?.Invoke(this, new SessionEventArgs(webSocketSession.SessionId));
|
|
}
|
|
}
|
|
|
|
public bool RenameSession(string oldSessionId, string newSessionId)
|
|
{
|
|
if (!Sessions.ContainsKey(oldSessionId) || Sessions.ContainsKey(newSessionId))
|
|
return false;
|
|
|
|
if (!Sessions.TryRemove(oldSessionId, out var webSocketSession))
|
|
return false;
|
|
|
|
webSocketSession.SessionId = newSessionId;
|
|
|
|
if (Sessions.TryAdd(newSessionId, webSocketSession))
|
|
return true;
|
|
|
|
webSocketSession.SessionId = oldSessionId;
|
|
|
|
if (!Sessions.TryAdd(oldSessionId, webSocketSession))
|
|
throw new Exception("Failed to add back the old session after failed rename");
|
|
|
|
return false;
|
|
}
|
|
|
|
public async Task SendToSession(string sessionId, string message)
|
|
{
|
|
try
|
|
{
|
|
if (Sessions.TryGetValue(sessionId, out var webSocketSession))
|
|
{
|
|
var buffer = Encoding.UTF8.GetBytes(message);
|
|
await webSocketSession.WebSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text,
|
|
true,
|
|
CancellationToken.None);
|
|
}
|
|
}
|
|
catch (WebSocketException ex)
|
|
{
|
|
if (ex.InnerException is SocketException { SocketErrorCode: SocketError.ConnectionReset })
|
|
{
|
|
if (Sessions.ContainsKey(sessionId))
|
|
Sessions.Remove(sessionId, out _);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class WsChatBotCommand
|
|
{
|
|
[JsonProperty("command")] public string Command { get; set; } = "";
|
|
|
|
[JsonProperty("requestId")] public string RequestId { get; set; } = "";
|
|
|
|
[JsonProperty("parameters")] public object[]? Parameters { get; set; }
|
|
}
|
|
|
|
internal class WsCommandResponder
|
|
{
|
|
private WebSocketBot _bot;
|
|
private string _sessionId;
|
|
private string _command;
|
|
private string _requestId;
|
|
|
|
public WsCommandResponder(WebSocketBot bot, string sessionId, string command, string requestId)
|
|
{
|
|
_bot = bot;
|
|
_sessionId = sessionId;
|
|
_command = command;
|
|
_requestId = requestId;
|
|
}
|
|
|
|
private void SendCommandResponse(bool success, string result, bool overrideAuth = false)
|
|
{
|
|
_bot.SendCommandResponse(_sessionId, success, _requestId, _command, result, overrideAuth);
|
|
}
|
|
|
|
public void SendErrorResponse(string error, bool overrideAuth = false)
|
|
{
|
|
SendCommandResponse(false, error, overrideAuth);
|
|
}
|
|
|
|
public void SendSuccessResponse(string result, bool overrideAuth = false)
|
|
{
|
|
SendCommandResponse(true, result, overrideAuth);
|
|
}
|
|
|
|
public void SendSuccessResponse(bool overrideAuth = false)
|
|
{
|
|
SendSuccessResponse(JsonConvert.SerializeObject(true), overrideAuth);
|
|
}
|
|
|
|
public string Quote(string text)
|
|
{
|
|
return $"\"{text}\"";
|
|
}
|
|
}
|
|
|
|
internal class NbtData
|
|
{
|
|
public NBT? nbt { get; set; }
|
|
}
|
|
|
|
internal class NBT
|
|
{
|
|
public Dictionary<string, object>? nbt { get; set; }
|
|
}
|
|
|
|
internal class NbtDictionaryConverter : JsonConverter<Dictionary<string, object>>
|
|
{
|
|
public override void WriteJson(JsonWriter writer, Dictionary<string, object>? value, JsonSerializer serializer)
|
|
=> throw new NotImplementedException();
|
|
|
|
public override Dictionary<string, object>? ReadJson(JsonReader reader, Type objectType,
|
|
Dictionary<string, object>? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
|
{
|
|
var keyValuePairs = serializer.Deserialize<IEnumerable<KeyValuePair<string, object>>>(reader);
|
|
return new(keyValuePairs!);
|
|
}
|
|
}
|
|
|
|
public class WebSocketBot : ChatBot
|
|
{
|
|
private string? _ip;
|
|
private int _port;
|
|
private string? _password;
|
|
private WebSocketServer? _server;
|
|
private List<string> _authenticatedSessions;
|
|
private List<(string, string)> _waitingEvents;
|
|
|
|
public static Configs Config = new();
|
|
|
|
[TomlDoNotInlineObject]
|
|
public class Configs
|
|
{
|
|
[NonSerialized] private const string BotName = "Websocket";
|
|
|
|
public bool Enabled = false;
|
|
|
|
[TomlInlineComment("$ChatBot.WebSocketBot.Ip$")]
|
|
public string? Ip = "127.0.0.1";
|
|
|
|
[TomlInlineComment("$ChatBot.WebSocketBot.Port$")]
|
|
public int Port = 8043;
|
|
|
|
[TomlInlineComment("$ChatBot.WebSocketBot.Password$")]
|
|
public string? Password = Guid.NewGuid().ToString().Replace("-", "").Trim().ToLower();
|
|
|
|
[TomlInlineComment("$ChatBot.WebSocketBot.DebugMode$")]
|
|
public bool DebugMode = false;
|
|
}
|
|
|
|
public WebSocketBot()
|
|
{
|
|
var match = Regex.Match(Config.Ip!, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}");
|
|
|
|
if (!match.Success)
|
|
{
|
|
LogToConsole(Translations.bot_WebSocketBot_failed_to_start_ip);
|
|
return;
|
|
}
|
|
|
|
if (Config.Port > 65535)
|
|
{
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_failed_to_start_port, _port.ToString()));
|
|
return;
|
|
}
|
|
|
|
_ip = Config.Ip;
|
|
_port = Config.Port;
|
|
_password = Config.Password;
|
|
_authenticatedSessions = new();
|
|
_waitingEvents = new();
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
_authenticatedSessions.Clear();
|
|
|
|
if (_server != null)
|
|
{
|
|
SendEvent("OnWsRestarting", "");
|
|
_server.Stop(); // If you await, this will freeze the task and the websocket won't work
|
|
_server = null;
|
|
}
|
|
|
|
try
|
|
{
|
|
LogToConsole(Translations.bot_WebSocketBot_starting);
|
|
_server = new();
|
|
_server.Start(_ip!, _port); // If you await, this will freeze the task and the websocket won't work
|
|
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_started, _ip, _port.ToString()));
|
|
|
|
foreach (var (eventName, data) in _waitingEvents)
|
|
SendEvent(eventName, data);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_failed_to_start_custom, e));
|
|
return;
|
|
}
|
|
|
|
_server.NewSession += (_, session) =>
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_new_session, session.SessionId));
|
|
_server.SessionDropped += (_, session) =>
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_disconnected, session.SessionId));
|
|
|
|
_server.MessageReceived += (_, messageObject) =>
|
|
{
|
|
if (!ProcessWebsocketCommand(messageObject.SessionId, _password!, messageObject.Message))
|
|
return;
|
|
|
|
var command = messageObject.Message;
|
|
command = command.StartsWith('/') ? command[1..] : $"send {command}";
|
|
|
|
CmdResult response = new();
|
|
PerformInternalCommand(command, ref response);
|
|
SendSessionEvent(messageObject.SessionId, "OnMccCommandResponse", $"{{\"response\": \"{response}\"}}");
|
|
};
|
|
});
|
|
}
|
|
|
|
private bool ProcessWebsocketCommand(string sessionId, string password, string message)
|
|
{
|
|
message = message.Trim();
|
|
|
|
if (string.IsNullOrEmpty(message))
|
|
return false;
|
|
|
|
if (message.StartsWith('{'))
|
|
{
|
|
try
|
|
{
|
|
if (Config.DebugMode)
|
|
LogDebugToConsole($"\n\n\tGot command\n\n\t{message}\n\n");
|
|
|
|
var cmd = JsonConvert.DeserializeObject<WsChatBotCommand>(message)!;
|
|
var responder = new WsCommandResponder(this, sessionId, cmd.Command, cmd.RequestId);
|
|
|
|
// Allow session name changing without authenticating for easier identification
|
|
if (cmd.Command.Equals("ChangeSessionId", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expected 1 (newSessionid)!"), true);
|
|
return false;
|
|
}
|
|
|
|
var newId = (cmd.Parameters[0] as string)!;
|
|
|
|
switch (newId.Length)
|
|
{
|
|
case 0:
|
|
responder.SendErrorResponse(responder.Quote("Please provide a valid session ID!"),
|
|
true);
|
|
return false;
|
|
case > 32:
|
|
responder.SendErrorResponse(
|
|
responder.Quote("The session ID can't be longer than 32 characters!"), true);
|
|
return false;
|
|
}
|
|
|
|
if (!_server!.RenameSession(sessionId, newId))
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Failed to change the session id to: '" + newId + "'"),
|
|
true);
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_id_failed_to_change, sessionId,
|
|
newId));
|
|
return false;
|
|
}
|
|
|
|
// If the session is authenticated, remove the old session id and add the new one
|
|
if (_authenticatedSessions.Contains(sessionId))
|
|
{
|
|
_authenticatedSessions.Remove(sessionId);
|
|
_authenticatedSessions.Add(newId);
|
|
}
|
|
|
|
responder.SendSuccessResponse(
|
|
responder.Quote("The session ID was successfully changed to: '" + newId + "'"), true);
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_id_changed, sessionId, newId));
|
|
return false;
|
|
}
|
|
|
|
// Authentication and session commands
|
|
if (password.Length != 0)
|
|
{
|
|
if (!_authenticatedSessions.Contains(sessionId))
|
|
{
|
|
// Special case for authentication
|
|
if (cmd.Command.Equals("Authenticate", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expected 1 (password)!"), true);
|
|
return false;
|
|
}
|
|
|
|
var pass = (cmd.Parameters[0] as string)!;
|
|
|
|
if (pass.Length == 0)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote(
|
|
"Please provide a valid password! (Example: 'Authenticate password123')"),
|
|
true);
|
|
return false;
|
|
}
|
|
|
|
if (!pass.Equals(password))
|
|
{
|
|
responder.SendErrorResponse(responder.Quote("Incorrect password provided!"), true);
|
|
return false;
|
|
}
|
|
|
|
_authenticatedSessions.Add(sessionId);
|
|
responder.SendSuccessResponse(responder.Quote("Successfully authenticated!"), true);
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_authenticated, sessionId));
|
|
return false;
|
|
}
|
|
|
|
responder.SendErrorResponse(
|
|
responder.Quote("You must authenticate in order to send and receive data!"), true);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!_authenticatedSessions.Contains(sessionId))
|
|
{
|
|
responder.SendSuccessResponse(responder.Quote("Successfully authenticated!"));
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_authenticated, sessionId));
|
|
_authenticatedSessions.Add(sessionId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Process other commands
|
|
switch (cmd.Command)
|
|
{
|
|
case "LogToConsole":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting a single parameter!"));
|
|
return false;
|
|
}
|
|
|
|
LogToConsole((cmd.Parameters[0] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "LogDebugToConsole":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting a single parameter!"));
|
|
return false;
|
|
}
|
|
|
|
LogDebugToConsole((cmd.Parameters[0] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "LogToConsoleTranslated":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting a single parameter!"));
|
|
return false;
|
|
}
|
|
|
|
LogToConsoleTranslated((cmd.Parameters[0] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "LogDebugToConsoleTranslated":
|
|
if (cmd.Parameters!.Length > 1 || cmd.Parameters.Length < 1)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting a single parameter!"));
|
|
return false;
|
|
}
|
|
|
|
LogDebugToConsoleTranslated((cmd.Parameters[0] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "ReconnectToTheServer":
|
|
if (cmd.Parameters is not { Length: 2 })
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 2 parameters (extraAttempts, delaySeconds)!"));
|
|
return false;
|
|
}
|
|
|
|
ReconnectToTheServer(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]));
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "DisconnectAndExit":
|
|
responder.SendSuccessResponse();
|
|
DisconnectAndExit();
|
|
break;
|
|
|
|
case "SendPrivateMessage":
|
|
if (cmd.Parameters is not { Length: 2 })
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 2 parameters (player, message)!"));
|
|
return false;
|
|
}
|
|
|
|
SendPrivateMessage((cmd.Parameters[0] as string)!, (cmd.Parameters[1] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "RunScript":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (filename)!"));
|
|
return false;
|
|
}
|
|
|
|
RunScript((cmd.Parameters[0] as string)!);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "GetTerrainEnabled":
|
|
responder.SendSuccessResponse(GetTerrainEnabled().ToString().ToLower());
|
|
break;
|
|
|
|
case "SetTerrainEnabled":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (enabled)!"));
|
|
return false;
|
|
}
|
|
|
|
SetTerrainEnabled((bool)cmd.Parameters[0]);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "GetEntityHandlingEnabled":
|
|
responder.SendSuccessResponse(GetEntityHandlingEnabled().ToString().ToLower());
|
|
break;
|
|
|
|
case "Sneak":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (on)!"));
|
|
return false;
|
|
}
|
|
|
|
Sneak((bool)cmd.Parameters[0]);
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "SendEntityAction":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (actionType)!"));
|
|
return false;
|
|
}
|
|
|
|
SendEntityAction(((Protocol.EntityActionType)(Convert.ToInt32(cmd.Parameters[0]))));
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "DigBlock":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 ||
|
|
cmd.Parameters.Length > 5)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 1 or 3 parameter(s) (location, swingArms?, lookAtBlock?)!"));
|
|
return false;
|
|
}
|
|
|
|
var location = new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2]));
|
|
|
|
if (location.DistanceSquared(GetCurrentLocation().EyesLocation()) > 25)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("The block you're trying to dig is too far away!"));
|
|
return false;
|
|
}
|
|
|
|
if (GetWorld().GetBlock(location).Type == Material.Air)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote("The block you're trying to dig is is air!"));
|
|
return false;
|
|
}
|
|
|
|
var result = cmd.Parameters.Length switch
|
|
{
|
|
3 => DigBlock(location),
|
|
4 => DigBlock(location, (bool)cmd.Parameters[3]),
|
|
5 => DigBlock(location, (bool)cmd.Parameters[3], (bool)cmd.Parameters[4]),
|
|
_ => false
|
|
};
|
|
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(result));
|
|
break;
|
|
|
|
case "SetSlot":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (slotNumber)!"));
|
|
return false;
|
|
}
|
|
|
|
SetSlot(Convert.ToInt32(cmd.Parameters[0]));
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "GetWorld":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetWorld()));
|
|
break;
|
|
|
|
case "GetEntities":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetEntities()));
|
|
break;
|
|
|
|
case "GetPlayersLatency":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPlayersLatency()));
|
|
break;
|
|
|
|
case "GetCurrentLocation":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetCurrentLocation()));
|
|
break;
|
|
|
|
case "MoveToLocation":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 ||
|
|
cmd.Parameters.Length > 8)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 1 or 7 parameter(s) (x, y, z, allowUnsafe?, allowDirectTeleport?, maxOffset?, minoffset?, timeout?)!"));
|
|
return false;
|
|
}
|
|
|
|
var allowUnsafe = false;
|
|
var allowDirectTeleport = false;
|
|
var maxOffset = 0;
|
|
var minOffset = 0;
|
|
TimeSpan? timeout = null;
|
|
|
|
if (cmd.Parameters.Length >= 4)
|
|
allowUnsafe = (bool)cmd.Parameters[3];
|
|
|
|
if (cmd.Parameters.Length >= 5)
|
|
allowDirectTeleport = (bool)cmd.Parameters[4];
|
|
|
|
if (cmd.Parameters.Length >= 6)
|
|
maxOffset = Convert.ToInt32(cmd.Parameters[5]);
|
|
|
|
if (cmd.Parameters.Length >= 7)
|
|
minOffset = Convert.ToInt32(cmd.Parameters[6]);
|
|
|
|
if (cmd.Parameters.Length == 8)
|
|
timeout = TimeSpan.FromSeconds(Convert.ToInt32(cmd.Parameters[7]));
|
|
|
|
var canMove = MoveToLocation(
|
|
new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]),
|
|
Convert.ToInt32(cmd.Parameters[2])),
|
|
allowUnsafe,
|
|
allowDirectTeleport,
|
|
maxOffset,
|
|
minOffset,
|
|
timeout);
|
|
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(canMove));
|
|
break;
|
|
|
|
case "ClientIsMoving":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(ClientIsMoving()));
|
|
break;
|
|
|
|
case "LookAtLocation":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 ||
|
|
cmd.Parameters.Length > 3)
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 3 parameter(s) (x, y, z)!"));
|
|
return false;
|
|
}
|
|
|
|
LookAtLocation(new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2])));
|
|
responder.SendSuccessResponse();
|
|
break;
|
|
|
|
case "GetTimestamp":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetTimestamp()));
|
|
break;
|
|
|
|
case "GetServerPort":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerPort()));
|
|
break;
|
|
|
|
case "GetServerHost":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerHost()));
|
|
break;
|
|
|
|
case "GetUsername":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetUsername()));
|
|
break;
|
|
|
|
case "GetGamemode":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GameModeString(GetGamemode())));
|
|
break;
|
|
|
|
case "GetYaw":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetYaw()));
|
|
break;
|
|
|
|
case "GetPitch":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPitch()));
|
|
break;
|
|
|
|
case "GetUserUUID":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetUserUUID()));
|
|
break;
|
|
|
|
case "GetOnlinePlayers":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetOnlinePlayers()));
|
|
break;
|
|
|
|
case "GetOnlinePlayersWithUUID":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetOnlinePlayersWithUUID()));
|
|
break;
|
|
|
|
case "GetServerTPS":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerTPS()));
|
|
break;
|
|
|
|
case "InteractEntity":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 2 ||
|
|
cmd.Parameters.Length > 3)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting at least 2 and at most 3 parameter(s) (entityId, interactionType, hand?)!"));
|
|
return false;
|
|
}
|
|
|
|
var interactionType = (InteractType)Convert.ToInt32(cmd.Parameters[1]);
|
|
var interactionHand = Hand.MainHand;
|
|
|
|
if (cmd.Parameters.Length == 3)
|
|
interactionHand = (Hand)Convert.ToInt32(cmd.Parameters[2]);
|
|
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(
|
|
InteractEntity(Convert.ToInt32(cmd.Parameters[0]), interactionType, interactionHand)));
|
|
break;
|
|
|
|
case "CreativeGive":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 ||
|
|
cmd.Parameters.Length > 4)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting at least 3 and at most 4 parameter(s) (slotId, itemType, count, nbt?)!"));
|
|
return false;
|
|
}
|
|
|
|
NBT? nbt = null;
|
|
|
|
if (cmd.Parameters.Length == 4)
|
|
nbt = JsonConvert.DeserializeObject<NBT>(cmd.Parameters[3].ToString()!,
|
|
new NbtDictionaryConverter())!;
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(CreativeGive(
|
|
Convert.ToInt32(cmd.Parameters[0]),
|
|
(ItemType)Convert.ToInt32(cmd.Parameters[1]),
|
|
Convert.ToInt32(cmd.Parameters[2]),
|
|
nbt == null ? new Dictionary<string, object>() : nbt!.nbt!)
|
|
));
|
|
|
|
break;
|
|
|
|
case "CreativeDelete":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting at 1 parameter (slotId)!"));
|
|
return false;
|
|
}
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(CreativeDelete(Convert.ToInt32(cmd.Parameters[0]))));
|
|
break;
|
|
|
|
case "SendAnimation":
|
|
var hand = Hand.MainHand;
|
|
|
|
if (cmd.Parameters is { Length: 1 })
|
|
hand = (Hand)Convert.ToInt32(cmd.Parameters[0]);
|
|
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(SendAnimation(hand)));
|
|
break;
|
|
|
|
case "SendPlaceBlock":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 4 ||
|
|
cmd.Parameters.Length > 4)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting at least 4 and at most 5 parameters (x, y, z, blockFace, hand?)!"));
|
|
return false;
|
|
}
|
|
|
|
var blockLocation = new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2]));
|
|
var blockFacingDirection = (Direction)Convert.ToInt32(cmd.Parameters[3]);
|
|
var handToUse = Hand.MainHand;
|
|
|
|
if (cmd.Parameters.Length == 4)
|
|
handToUse = (Hand)Convert.ToInt32(cmd.Parameters[4]);
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(SendPlaceBlock(blockLocation, blockFacingDirection,
|
|
handToUse)));
|
|
break;
|
|
|
|
case "UseItemInHand":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(UseItemInHand()));
|
|
break;
|
|
|
|
case "GetInventoryEnabled":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetInventoryEnabled()));
|
|
break;
|
|
|
|
case "GetPlayerInventory":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPlayerInventory()));
|
|
break;
|
|
|
|
case "GetInventories":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetInventories()));
|
|
break;
|
|
|
|
case "WindowAction":
|
|
if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length != 3)
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 3 parameters (inventoryId, slotId, windowActionType)!"));
|
|
return false;
|
|
}
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(WindowAction(
|
|
Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]),
|
|
(WindowActionType)Convert.ToInt32(cmd.Parameters[2])
|
|
)));
|
|
break;
|
|
|
|
case "ChangeSlot":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (slotId)!"));
|
|
return false;
|
|
}
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(ChangeSlot((short)Convert.ToInt32(cmd.Parameters[0]))));
|
|
break;
|
|
|
|
case "GetCurrentSlot":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetCurrentSlot()));
|
|
break;
|
|
|
|
case "ClearInventories":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(ClearInventories()));
|
|
break;
|
|
|
|
case "UpdateSign":
|
|
if (cmd.Parameters is not { Length: 7 })
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 1 parameter (x, y, z, line1, line2, line3, line4)!"));
|
|
return false;
|
|
}
|
|
|
|
var signLocation = new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2]));
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(UpdateSign(signLocation,
|
|
(string)cmd.Parameters[3],
|
|
(string)cmd.Parameters[4],
|
|
(string)cmd.Parameters[5],
|
|
(string)cmd.Parameters[6]
|
|
)));
|
|
break;
|
|
|
|
case "SelectTrade":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (selectedSlot)!"));
|
|
return false;
|
|
}
|
|
|
|
responder.SendSuccessResponse(
|
|
JsonConvert.SerializeObject(SelectTrade(Convert.ToInt32(cmd.Parameters[0]))));
|
|
break;
|
|
|
|
case "UpdateCommandBlock":
|
|
if (cmd.Parameters is not { Length: 6 })
|
|
{
|
|
responder.SendErrorResponse(responder.Quote(
|
|
"Invalid number of parameters, expecting 1 parameter (x, y, z, command, commandBlockMode, commandBlockFlags)!"));
|
|
return false;
|
|
}
|
|
|
|
var commandBlockLocation = new Location(Convert.ToInt32(cmd.Parameters[0]),
|
|
Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2]));
|
|
|
|
responder.SendSuccessResponse(
|
|
UpdateCommandBlock(commandBlockLocation,
|
|
(string)cmd.Parameters[3],
|
|
(CommandBlockMode)Convert.ToInt32(cmd.Parameters[4]),
|
|
(CommandBlockFlags)Convert.ToInt32(cmd.Parameters[5])
|
|
).ToString().ToLower());
|
|
break;
|
|
|
|
case "CloseInventory":
|
|
if (cmd.Parameters is not { Length: 1 })
|
|
{
|
|
responder.SendErrorResponse(
|
|
responder.Quote("Invalid number of parameters, expecting 1 parameter (inventoryId)!"));
|
|
return false;
|
|
}
|
|
|
|
responder.SendSuccessResponse(CloseInventory(Convert.ToInt32(cmd.Parameters[0])).ToString()
|
|
.ToLower());
|
|
break;
|
|
|
|
case "GetMaxChatMessageLength":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetMaxChatMessageLength()));
|
|
break;
|
|
|
|
case "Respawn":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(Respawn()));
|
|
break;
|
|
|
|
case "GetProtocolVersion":
|
|
responder.SendSuccessResponse(JsonConvert.SerializeObject(GetProtocolVersion()));
|
|
break;
|
|
|
|
default:
|
|
responder.SendErrorResponse(
|
|
responder.Quote($"Unknown command {cmd.Command} received!"));
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogDebugToConsole(e.Message);
|
|
SendSessionEvent(sessionId, "OnWsCommandResponse",
|
|
"{\"success\": false, \"message\": \"An error occured, possible reasons: mail-formed json, type conversion, internal error\", \"stackTrace\": \"" +
|
|
Json.EscapeString(e.ToString()) + "\"}", true);
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (password.Length != 0)
|
|
{
|
|
if (!_authenticatedSessions.Contains(sessionId))
|
|
{
|
|
SendSessionEvent(sessionId, "OnWsCommandResponse",
|
|
"{\"error\": true, \"message\": \"You must authenticate in order to send and receive data!\"}",
|
|
true);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!_authenticatedSessions.Contains(sessionId))
|
|
{
|
|
SendSessionEvent(sessionId, "OnWsCommandResponse",
|
|
"{\"success\": true, \"message\": \"Successfully authenticated!\"}", true);
|
|
LogToConsole(string.Format(Translations.bot_WebSocketBot_session_authenticated, sessionId));
|
|
_authenticatedSessions.Add(sessionId);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void OnUnload()
|
|
{
|
|
if (_server != null)
|
|
{
|
|
SendEvent("OnWsConnectionClose", "");
|
|
_server.Stop();
|
|
_server = null;
|
|
}
|
|
|
|
_authenticatedSessions.Clear();
|
|
}
|
|
|
|
// ==========================================================================================
|
|
// Bot Events
|
|
// ==========================================================================================
|
|
public override void AfterGameJoined()
|
|
{
|
|
// Workaround to wait until the WebSocket server has been started
|
|
// This would fire before the WS server is started, this causing a null exception.
|
|
_waitingEvents.Add(("OnGameJoined", ""));
|
|
}
|
|
|
|
public override void OnBlockBreakAnimation(Entity entity, Location location, byte stage)
|
|
{
|
|
SendEvent("OnBlockBreakAnimation", new { entity, location, stage });
|
|
}
|
|
|
|
public override void OnEntityAnimation(Entity entity, byte animation)
|
|
{
|
|
SendEvent("OnEntityAnimation", new { entity, animation });
|
|
}
|
|
|
|
public override void GetText(string text)
|
|
{
|
|
text = GetVerbatim(text).Trim();
|
|
|
|
var message = "";
|
|
var username = "";
|
|
|
|
if (IsPrivateMessage(text, ref message, ref username))
|
|
SendEvent("OnChatPrivate", new { sender = username, message, rawText = text });
|
|
else if (IsChatMessage(text, ref message, ref username))
|
|
SendEvent("OnChatPublic", new { username, message, rawText = text });
|
|
else if (IsTeleportRequest(text, ref username))
|
|
SendEvent("OnTeleportRequest", new { sender = username, rawText = text });
|
|
}
|
|
|
|
public override void GetText(string text, string? json)
|
|
{
|
|
SendEvent("OnChatRaw", new { text, json });
|
|
}
|
|
|
|
public override bool OnDisconnect(DisconnectReason reason, string message)
|
|
{
|
|
var reasonString = reason switch
|
|
{
|
|
DisconnectReason.ConnectionLost => "Connection Lost",
|
|
DisconnectReason.UserLogout => "User Logout",
|
|
DisconnectReason.InGameKick => "In-Game Kick",
|
|
DisconnectReason.LoginRejected => "Login Rejected",
|
|
_ => "Unknown"
|
|
};
|
|
|
|
SendEvent("OnDisconnect", new { reason = reasonString, message });
|
|
return false;
|
|
}
|
|
|
|
public override void OnPlayerProperty(Dictionary<string, double> prop)
|
|
{
|
|
SendEvent("OnPlayerProperty", prop);
|
|
}
|
|
|
|
public override void OnServerTpsUpdate(double tps)
|
|
{
|
|
SendEvent("OnServerTpsUpdate", new { tps });
|
|
}
|
|
|
|
public override void OnTimeUpdate(long worldAge, long timeOfDay)
|
|
{
|
|
SendEvent("OnTimeUpdate", new { worldAge, timeOfDay });
|
|
}
|
|
|
|
public override void OnEntityMove(Entity entity)
|
|
{
|
|
SendEvent("OnEntityMove", entity);
|
|
}
|
|
|
|
public override void OnInternalCommand(string commandName, string commandParams, CmdResult result)
|
|
{
|
|
SendEvent("OnInternalCommand",
|
|
new { command = commandName, parameters = commandParams, result = result.ToString().Replace("\"", "'") });
|
|
}
|
|
|
|
public override void OnEntitySpawn(Entity entity)
|
|
{
|
|
SendEvent("OnEntitySpawn", entity);
|
|
}
|
|
|
|
public override void OnEntityDespawn(Entity entity)
|
|
{
|
|
SendEvent("OnEntityDespawn", entity);
|
|
}
|
|
|
|
public override void OnHeldItemChange(byte slot)
|
|
{
|
|
SendEvent("OnHeldItemChange", new { itemSlot = slot });
|
|
}
|
|
|
|
public override void OnHealthUpdate(float health, int food)
|
|
{
|
|
SendEvent("OnHealthUpdate", new { health, food });
|
|
}
|
|
|
|
public override void OnExplosion(Location explode, float strength, int recordCount)
|
|
{
|
|
SendEvent("OnExplosion", new { location = explode, strength, recordCount });
|
|
}
|
|
|
|
public override void OnSetExperience(float experienceBar, int level, int totalExperience)
|
|
{
|
|
SendEvent("OnSetExperience",
|
|
new { experienceBar, level, totalExperience });
|
|
}
|
|
|
|
public override void OnGamemodeUpdate(string playerName, Guid uuid, int gameMode)
|
|
{
|
|
SendEvent("OnGamemodeUpdate", new { playerName, uuid, gameMode = GameModeString(gameMode) });
|
|
}
|
|
|
|
public override void OnLatencyUpdate(string playerName, Guid uuid, int latency)
|
|
{
|
|
SendEvent("OnLatencyUpdate", new { playerName, uuid, latency });
|
|
}
|
|
|
|
public override void OnMapData(int mapId, byte scale, bool trackingPosition, bool locked, List<MapIcon> icons,
|
|
byte columnsUpdated, byte rowsUpdated, byte mapColumnX, byte mapRowZ, byte[]? colors)
|
|
{
|
|
SendEvent("OnMapData",
|
|
new
|
|
{
|
|
mapId, scale, trackingPosition, locked, icons, columnsUpdated, rowsUpdated, mapColumnX, mapRowZ,
|
|
colors
|
|
});
|
|
}
|
|
|
|
public override void OnTradeList(int windowId, List<VillagerTrade> trades, VillagerInfo villagerInfo)
|
|
{
|
|
SendEvent("OnTradeList", new { windowId, trades, villagerInfo });
|
|
}
|
|
|
|
public override void OnTitle(int action, string titleText, string subtitleText, string actionBarText, int fadein,
|
|
int stay, int fadeout, string json_)
|
|
{
|
|
SendEvent("OnTitle",
|
|
new
|
|
{
|
|
action, titleText, subtitleText, actionBarText,
|
|
fadeIn = fadein, stay, rawJson = json_
|
|
});
|
|
}
|
|
|
|
public override void OnEntityEquipment(Entity entity, int slot, Item? item)
|
|
{
|
|
SendEvent("OnEntityEquipment", new { entity, slot, item });
|
|
}
|
|
|
|
public override void OnEntityEffect(Entity entity, Effects effect, int amplifier, int duration, byte flags)
|
|
{
|
|
SendEvent("OnEntityEffect", new { entity, effect, amplifier, duration, flags });
|
|
}
|
|
|
|
public override void OnScoreboardObjective(string objectiveName, byte mode, string objectiveValue, int type,
|
|
string json_)
|
|
{
|
|
SendEvent("OnScoreboardObjective",
|
|
new { objectiveName, mode, objectiveValue, type, rawJson = json_ });
|
|
}
|
|
|
|
public override void OnUpdateScore(string entityName, int action, string objectiveName, int value)
|
|
{
|
|
SendEvent("OnUpdateScore",
|
|
new { entityName, action, objectiveName, type = value });
|
|
}
|
|
|
|
public override void OnInventoryUpdate(int inventoryId)
|
|
{
|
|
SendEvent("OnInventoryUpdate", new { inventoryId });
|
|
}
|
|
|
|
public override void OnInventoryOpen(int inventoryId)
|
|
{
|
|
SendEvent("OnInventoryOpen", new { inventoryId });
|
|
}
|
|
|
|
public override void OnInventoryClose(int inventoryId)
|
|
{
|
|
SendEvent("OnInventoryClose", new { inventoryId });
|
|
}
|
|
|
|
public override void OnPlayerJoin(Guid uuid, string name)
|
|
{
|
|
SendEvent("OnPlayerJoin", new { uuid, name });
|
|
}
|
|
|
|
public override void OnPlayerLeave(Guid uuid, string? name)
|
|
{
|
|
SendEvent("OnPlayerLeave", new { uuid, name = name ?? "null" });
|
|
}
|
|
|
|
public override void OnDeath()
|
|
{
|
|
SendEvent("OnDeath", "");
|
|
}
|
|
|
|
public override void OnRespawn()
|
|
{
|
|
SendEvent("OnRespawn", "");
|
|
}
|
|
|
|
public override void OnEntityHealth(Entity entity, float health)
|
|
{
|
|
SendEvent("OnEntityHealth", new { entity, health });
|
|
}
|
|
|
|
public override void OnEntityMetadata(Entity entity, Dictionary<int, object?>? metadata)
|
|
{
|
|
SendEvent("OnEntityMetadata", new { entity, metadata });
|
|
}
|
|
|
|
public override void OnPlayerStatus(byte statusId)
|
|
{
|
|
SendEvent("OnPlayerStatus", new { statusId });
|
|
}
|
|
|
|
public override void OnNetworkPacket(int packetID, List<byte> packetData, bool isLogin, bool isInbound)
|
|
{
|
|
SendEvent("OnNetworkPacket", new { packetId = packetID, isLogin, isInbound, packetData });
|
|
}
|
|
|
|
// ==========================================================================================
|
|
// Helper methods
|
|
// ==========================================================================================
|
|
|
|
private void SendEvent(string type, object data, bool overrideAuth = false)
|
|
{
|
|
if (_server == null)
|
|
return;
|
|
|
|
foreach (var (sessionId, _) in _server!.Sessions)
|
|
SendSessionEvent(sessionId, type, JsonConvert.SerializeObject(data), overrideAuth);
|
|
}
|
|
|
|
private void SendEvent(string type, string data, bool overrideAuth = false)
|
|
{
|
|
if (_server == null)
|
|
return;
|
|
|
|
foreach (var (sessionId, _) in _server.Sessions)
|
|
SendSessionEvent(sessionId, type, data, overrideAuth);
|
|
}
|
|
|
|
private void SendSessionEvent(string sessionId, string type, string data, bool overrideAuth = false)
|
|
{
|
|
if (sessionId.Length > 0 && (overrideAuth || _authenticatedSessions.Contains(sessionId)))
|
|
{
|
|
_server?.SendToSession(sessionId,
|
|
$"{{\"event\": \"{type}\", \"data\": {(string.IsNullOrEmpty(data) ? "null" : $"\"{Json.EscapeString(data)}\"")}}}")
|
|
.Wait();
|
|
|
|
if (!(type.Contains("Entity") || type.Equals("OnTimeUpdate") || type.Equals("OnServerTpsUpdate")) &&
|
|
Config.DebugMode)
|
|
LogDebugToConsole(
|
|
$"\n\n\tSending:\n\n\t{{\"event\": \"{type}\", \"data\": {(string.IsNullOrEmpty(data)
|
|
? "null"
|
|
: $"\"{Json.EscapeString(data)}\"")}}}\n\n");
|
|
}
|
|
}
|
|
|
|
public void SendCommandResponse(string sessionId, bool success, string requestId, string command,
|
|
string result, bool overrideAuth = false)
|
|
{
|
|
SendSessionEvent(sessionId, "OnWsCommandResponse",
|
|
$"{{\"success\": {success.ToString().ToLower()}, \"requestId\": \"{requestId}\", \"command\": \"{command}\", \"result\": {(string.IsNullOrEmpty(result) ? "null" : result)}}}",
|
|
overrideAuth);
|
|
}
|
|
|
|
private static string GameModeString(int gameMode)
|
|
{
|
|
return gameMode switch
|
|
{
|
|
0 => "survival",
|
|
1 => "creative",
|
|
2 => "adventure",
|
|
3 => "spectator",
|
|
_ => "unknown"
|
|
};
|
|
}
|
|
} |