2022-11-28 13:55:05 +08:00
using System ;
2022-12-20 22:41:14 +08:00
using System.Collections.Concurrent ;
2020-05-26 11:20:12 +02:00
using System.Collections.Generic ;
using System.Linq ;
2022-12-20 22:41:14 +08:00
using System.Net.Http ;
2020-05-26 11:20:12 +02:00
using System.Net.Sockets ;
2022-10-02 18:31:08 +08:00
using System.Text ;
2020-05-26 11:20:12 +02:00
using System.Threading ;
2022-12-20 22:41:14 +08:00
using System.Threading.Tasks ;
2022-10-26 08:54:54 +08:00
using Brigadier.NET ;
using Brigadier.NET.Exceptions ;
2020-05-26 11:20:12 +02:00
using MinecraftClient.ChatBots ;
2022-12-06 15:50:17 +08:00
using MinecraftClient.CommandHandler ;
using MinecraftClient.CommandHandler.Patch ;
2020-05-26 11:20:12 +02:00
using MinecraftClient.Inventory ;
2021-03-07 14:23:26 +08:00
using MinecraftClient.Logger ;
2022-12-23 00:50:20 +08:00
using MinecraftClient.EntityHandler ;
2022-10-02 18:31:08 +08:00
using MinecraftClient.Mapping ;
using MinecraftClient.Protocol ;
using MinecraftClient.Protocol.Handlers.Forge ;
2022-08-27 02:10:44 +08:00
using MinecraftClient.Protocol.Message ;
2022-12-06 15:50:17 +08:00
using MinecraftClient.Protocol.ProfileKey ;
2022-10-02 18:31:08 +08:00
using MinecraftClient.Protocol.Session ;
using MinecraftClient.Proxy ;
2022-12-06 15:50:17 +08:00
using MinecraftClient.Scripting ;
2022-10-05 15:02:30 +08:00
using static MinecraftClient . Settings ;
2020-05-26 11:20:12 +02:00
namespace MinecraftClient
{
/// <summary>
/// The main client class, used to connect to a Minecraft server.
/// </summary>
2020-06-20 15:01:16 +02:00
public class McClient : IMinecraftComHandler
2020-05-26 11:20:12 +02:00
{
public static int ReconnectionAttemptsLeft = 0 ;
2022-12-11 16:30:45 +08:00
public static CommandDispatcher < CmdResult > dispatcher = new ( ) ;
2021-03-27 19:15:58 +01:00
2022-12-23 00:50:20 +08:00
private readonly Dictionary < Guid , PlayerInfo > onlinePlayers = new ( ) ;
2020-05-26 11:20:12 +02:00
2022-12-20 22:41:14 +08:00
private readonly ConcurrentQueue < string > chatQueue = new ( ) ;
2022-12-23 00:50:20 +08:00
private DateTime nextMessageSendTime = DateTime . MinValue ;
2020-10-24 17:41:35 +02:00
2022-12-23 00:50:20 +08:00
private readonly object inventoryLock = new ( ) ;
private readonly Dictionary < int , Container > inventories = new ( ) ;
2020-05-26 11:20:12 +02:00
2022-08-15 23:55:44 +08:00
private readonly List < string > registeredServerPluginChannels = new ( ) ;
2022-12-20 22:41:14 +08:00
private readonly Dictionary < string , List < ChatBot > > registeredBotPluginChannels = new ( ) ;
2020-05-26 11:20:12 +02:00
private bool terrainAndMovementsEnabled ;
private bool terrainAndMovementsRequested = false ;
private bool inventoryHandlingEnabled ;
private bool inventoryHandlingRequested = false ;
private bool entityHandlingEnabled ;
2022-12-23 00:50:20 +08:00
private static SemaphoreSlim locationLock = new ( 1 , 1 ) ;
2020-05-26 11:20:12 +02:00
private bool locationReceived = false ;
2022-10-02 18:31:08 +08:00
private readonly World world = new ( ) ;
private Queue < Location > ? steps ;
2022-09-04 17:34:12 +08:00
private Queue < Location > ? path ;
2020-05-26 11:20:12 +02:00
private Location location ;
2021-03-13 22:23:58 +08:00
private float? _yaw ; // Used for calculation ONLY!!! Doesn't reflect the client yaw
private float? _pitch ; // Used for calculation ONLY!!! Doesn't reflect the client pitch
private float playerYaw ;
private float playerPitch ;
2020-05-26 11:20:12 +02:00
private double motionY ;
2022-08-29 19:12:44 +02:00
public enum MovementType { Sneak , Walk , Sprint }
2022-08-15 23:55:44 +08:00
private int sequenceId ; // User for player block synchronization (Aka. digging, placing blocks, etc..)
2020-05-26 11:20:12 +02:00
2022-10-02 18:31:08 +08:00
private readonly string host ;
private readonly int port ;
2022-12-20 22:41:14 +08:00
private int protocolversion ;
private string username ;
2022-08-27 02:10:44 +08:00
private Guid uuid ;
private string uuidStr ;
2022-12-20 22:41:14 +08:00
private string sessionId ;
private PlayerKeyPair ? playerKeyPair ;
2020-05-26 11:20:12 +02:00
private DateTime lastKeepAlive ;
private int respawnTicks = 0 ;
2020-06-13 18:00:30 +05:00
private int gamemode = 0 ;
2022-08-15 23:55:44 +08:00
private bool isSupportPreviewsChat ;
2022-10-17 15:14:55 +02:00
private EnchantmentData ? lastEnchantment = null ;
2020-05-26 11:20:12 +02:00
private int playerEntityID ;
// player health and hunger
private float playerHealth ;
private int playerFoodSaturation ;
2020-05-29 23:18:34 +05:00
private int playerLevel ;
private int playerTotalExperience ;
2020-05-26 11:20:12 +02:00
private byte CurrentSlot = 0 ;
// Entity handling
2022-10-02 18:31:08 +08:00
private readonly Dictionary < int , Entity > entities = new ( ) ;
2020-05-26 11:20:12 +02:00
// server TPS
private long lastAge = 0 ;
private DateTime lastTime ;
2020-08-17 17:40:06 +08:00
private double serverTPS = 0 ;
private double averageTPS = 20 ;
private const int maxSamples = 5 ;
2022-10-02 18:31:08 +08:00
private readonly List < double > tpsSamples = new ( maxSamples ) ;
2020-08-17 17:40:06 +08:00
private double sampleSum = 0 ;
2022-12-20 22:41:14 +08:00
// ChatBot
2022-12-21 14:01:05 +08:00
private ChatBot [ ] chatbots = Array . Empty < ChatBot > ( ) ;
private static ChatBot [ ] botsOnHold = Array . Empty < ChatBot > ( ) ;
2022-12-20 22:41:14 +08:00
private bool OldChatBotUpdateTrigger = false ;
2020-09-07 03:51:42 +08:00
private bool networkPacketCaptureEnabled = false ;
2022-12-23 00:50:20 +08:00
// ChatBot Async Events
private static int EventTypeCount = typeof ( McClientEventType ) . GetFields ( ) . Length ;
private static SemaphoreSlim EventCallbackWriteLock = new ( 1 , 1 ) ;
private static Task [ ] [ ] ChatbotEventTasks = new Task [ EventTypeCount ] [ ] ;
private static Task [ ] WaitChatbotExecuteTask = new Task [ EventTypeCount ] ;
private static SemaphoreSlim [ ] ChatbotEventTaskLocks = new SemaphoreSlim [ EventTypeCount ] ;
private static Func < object? , Task > [ ] [ ] ChatbotEvents = new Func < object? , Task > [ EventTypeCount ] [ ] ;
private static Dictionary < ChatBot , List < Tuple < McClientEventType , Func < object? , Task > > > > ChatbotRegisteredEvents = new ( ) ;
2020-05-26 11:20:12 +02:00
public int GetServerPort ( ) { return port ; }
public string GetServerHost ( ) { return host ; }
public string GetUsername ( ) { return username ; }
2022-08-27 02:10:44 +08:00
public Guid GetUserUuid ( ) { return uuid ; }
public string GetUserUuidStr ( ) { return uuidStr ; }
2022-12-20 22:41:14 +08:00
public string GetSessionID ( ) { return sessionId ; }
2020-05-26 11:20:12 +02:00
public Location GetCurrentLocation ( ) { return location ; }
2021-03-13 22:23:58 +08:00
public float GetYaw ( ) { return playerYaw ; }
2022-08-15 23:55:44 +08:00
public int GetSequenceId ( ) { return sequenceId ; }
2021-03-13 22:23:58 +08:00
public float GetPitch ( ) { return playerPitch ; }
2020-05-26 11:20:12 +02:00
public World GetWorld ( ) { return world ; }
2022-08-31 20:46:21 +08:00
public double GetServerTPS ( ) { return averageTPS ; }
2022-08-15 23:55:44 +08:00
public bool GetIsSupportPreviewsChat ( ) { return isSupportPreviewsChat ; }
2020-05-26 11:20:12 +02:00
public float GetHealth ( ) { return playerHealth ; }
public int GetSaturation ( ) { return playerFoodSaturation ; }
2020-05-29 23:18:34 +05:00
public int GetLevel ( ) { return playerLevel ; }
public int GetTotalExperience ( ) { return playerTotalExperience ; }
2020-05-26 11:20:12 +02:00
public byte GetCurrentSlot ( ) { return CurrentSlot ; }
2020-06-13 18:00:30 +05:00
public int GetGamemode ( ) { return gamemode ; }
2020-09-07 03:51:42 +08:00
public bool GetNetworkPacketCaptureEnabled ( ) { return networkPacketCaptureEnabled ; }
2020-10-31 03:35:03 +08:00
public int GetProtocolVersion ( ) { return protocolversion ; }
2022-10-02 18:31:08 +08:00
public ILogger GetLogger ( ) { return Log ; }
2021-03-21 22:17:19 +08:00
public int GetPlayerEntityID ( ) { return playerEntityID ; }
2022-12-21 14:01:05 +08:00
public ChatBot [ ] GetLoadedChatBots ( ) { return chatbots ; }
2020-05-26 11:20:12 +02:00
2022-12-20 22:41:14 +08:00
private TcpClient ? tcpClient ;
private IMinecraftCom ? handler ;
2022-12-21 14:01:05 +08:00
private readonly CancellationTokenSource CancelTokenSource ;
2020-05-26 11:20:12 +02:00
2021-01-29 07:45:18 +08:00
public ILogger Log ;
2022-12-21 14:01:05 +08:00
public static void LoadCommandsAndChatbots ( )
{
2022-12-23 00:50:20 +08:00
for ( int i = 0 ; i < EventTypeCount ; + + i )
{
ChatbotEventTaskLocks [ i ] = new ( 1 , 1 ) ;
WaitChatbotExecuteTask [ i ] = Task . CompletedTask ;
}
2022-12-21 14:01:05 +08:00
/* Load commands from the 'Commands' namespace */
Type [ ] cmds_classes = Program . GetTypesInNamespace ( "MinecraftClient.Commands" ) ;
foreach ( Type type in cmds_classes )
{
if ( type . IsSubclassOf ( typeof ( Command ) ) )
{
Command cmd = ( Command ) Activator . CreateInstance ( type ) ! ;
cmd . RegisterCommand ( dispatcher ) ;
}
}
/* Load ChatBots */
2022-12-23 00:50:20 +08:00
botsOnHold = GetChatbotsToRegister ( ) ;
2022-12-21 14:01:05 +08:00
foreach ( ChatBot bot in botsOnHold )
bot . Initialize ( ) ;
2022-12-23 00:50:20 +08:00
InitializeChatbotEventCallbacks ( botsOnHold ) . Wait ( ) ;
2022-12-21 14:01:05 +08:00
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// </summary>
2022-08-31 20:46:21 +08:00
/// <param name="session">A valid session obtained with MinecraftCom.GetLogin()</param>
/// <param name="playerKeyPair">Key for message signing</param>
2022-12-20 22:41:14 +08:00
/// <param name="serverHost">The server IP</param>
/// <param name="serverPort">The server port to use</param>
2020-05-26 11:20:12 +02:00
/// <param name="protocolversion">Minecraft protocol version to use</param>
2022-08-31 20:46:21 +08:00
/// <param name="forgeInfo">ForgeInfo item stating that Forge is enabled</param>
2022-12-21 14:01:05 +08:00
public McClient ( string serverHost , ushort serverPort , CancellationTokenSource cancelTokenSource )
2020-05-26 11:20:12 +02:00
{
2022-12-21 14:01:05 +08:00
CancelTokenSource = cancelTokenSource ;
2022-12-20 22:41:14 +08:00
2022-12-11 16:30:45 +08:00
CmdResult . currentHandler = this ;
2022-10-05 15:02:30 +08:00
terrainAndMovementsEnabled = Config . Main . Advanced . TerrainAndMovements ;
inventoryHandlingEnabled = Config . Main . Advanced . InventoryHandling ;
entityHandlingEnabled = Config . Main . Advanced . EntityHandling ;
2020-05-26 11:20:12 +02:00
2022-12-20 22:41:14 +08:00
host = serverHost ;
port = serverPort ;
uuid = Guid . Empty ;
uuidStr = string . Empty ;
username = string . Empty ;
sessionId = string . Empty ;
playerKeyPair = null ;
protocolversion = 0 ;
2020-05-26 11:20:12 +02:00
2022-12-20 22:41:14 +08:00
Log = Config . Logging . LogToFile
2022-10-05 15:02:30 +08:00
? new FileLogLogger ( Config . AppVar . ExpandVars ( Settings . Config . Logging . LogFile ) , Settings . Config . Logging . PrependTimestamp )
2021-03-07 14:23:26 +08:00
: new FilteredLogger ( ) ;
2022-10-05 15:02:30 +08:00
Log . DebugEnabled = Config . Logging . DebugMessages ;
Log . InfoEnabled = Config . Logging . InfoMessages ;
Log . ChatEnabled = Config . Logging . ChatMessages ;
Log . WarnEnabled = Config . Logging . WarningMessages ;
Log . ErrorEnabled = Config . Logging . ErrorMessages ;
2022-12-23 00:50:20 +08:00
ClearInventories ( ) ;
chatbots = botsOnHold ;
botsOnHold = Array . Empty < ChatBot > ( ) ;
foreach ( ChatBot bot in chatbots )
bot . SetHandler ( this ) ;
2022-12-20 22:41:14 +08:00
}
public async Task Login ( HttpClient httpClient , SessionToken session , PlayerKeyPair ? playerKeyPair , int protocolversion , ForgeInfo ? forgeInfo )
{
sessionId = session . ID ;
if ( ! Guid . TryParse ( session . PlayerID , out uuid ) )
uuid = Guid . Empty ;
uuidStr = session . PlayerID ;
username = session . PlayerName ;
this . playerKeyPair = playerKeyPair ;
this . protocolversion = protocolversion ;
2020-05-26 11:20:12 +02:00
try
{
2022-12-20 22:41:14 +08:00
tcpClient = ProxyHandler . NewTcpClient ( host , port , ProxyHandler . ClientType . Ingame ) ;
tcpClient . ReceiveBufferSize = 1024 * 1024 ;
tcpClient . ReceiveTimeout = Config . Main . Advanced . TcpTimeout * 1000 ; // Default: 30 seconds
2022-12-21 14:01:05 +08:00
handler = ProtocolHandler . GetProtocolHandler ( CancelTokenSource . Token , tcpClient , protocolversion , forgeInfo , this ) ;
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . mcc_version_supported ) ;
2020-05-26 11:20:12 +02:00
2022-12-21 14:01:05 +08:00
_ = Task . Run ( TimeoutDetector , CancelTokenSource . Token ) ;
2021-04-30 12:28:27 +08:00
2020-05-26 11:20:12 +02:00
try
{
2022-12-20 22:41:14 +08:00
if ( await handler . Login ( httpClient , this . playerKeyPair , session ) )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
DispatchBotEvent ( bot = > bot . AfterGameJoined ( ) ) ;
await TriggerEvent ( McClientEventType . GameJoin , null ) ;
2022-12-20 22:41:14 +08:00
return ;
2020-05-26 11:20:12 +02:00
}
2022-12-20 22:41:14 +08:00
Log . Error ( Translations . error_login_failed ) ;
2020-05-26 11:20:12 +02:00
}
catch ( Exception e )
{
2022-12-20 22:41:14 +08:00
Log . Error ( $"{e.GetType().Name}: {e.Message}" ) ;
if ( e . StackTrace ! = null )
Log . Error ( e . StackTrace ) ;
2022-10-28 11:13:20 +08:00
Log . Error ( Translations . error_join ) ;
2020-05-26 11:20:12 +02:00
}
}
catch ( SocketException e )
{
2021-01-29 07:45:18 +08:00
Log . Error ( e . Message ) ;
2022-10-28 11:13:20 +08:00
Log . Error ( Translations . error_connect ) ;
2020-05-26 11:20:12 +02:00
}
2022-10-02 18:31:08 +08:00
if ( ReconnectionAttemptsLeft > 0 )
{
2022-10-28 11:13:20 +08:00
Log . Info ( string . Format ( Translations . mcc_reconnect , ReconnectionAttemptsLeft ) ) ;
2022-10-02 18:31:08 +08:00
Thread . Sleep ( 5000 ) ;
ReconnectionAttemptsLeft - - ;
2022-12-21 14:01:05 +08:00
Program . SetRestart ( ) ;
2020-05-26 11:20:12 +02:00
}
2022-12-21 14:01:05 +08:00
else
2022-10-02 18:31:08 +08:00
{
2022-12-21 14:01:05 +08:00
Program . SetExit ( ) ;
2022-10-02 18:31:08 +08:00
}
throw new Exception ( "Initialization failed." ) ;
2020-05-26 11:20:12 +02:00
}
2022-12-20 22:41:14 +08:00
public async Task StartUpdating ( )
{
2022-12-21 14:01:05 +08:00
Log . Info ( string . Format ( Translations . mcc_joined , Config . Main . Advanced . InternalCmdChar . ToLogString ( ) ) ) ;
ConsoleInteractive . ConsoleReader . MessageReceived + = ConsoleReaderOnMessageReceived ;
ConsoleInteractive . ConsoleReader . OnInputChange + = ConsoleIO . AutocompleteHandler ;
ConsoleInteractive . ConsoleReader . BeginReadThread ( ) ;
2022-12-20 22:41:14 +08:00
await handler ! . StartUpdating ( ) ;
2022-12-21 14:01:05 +08:00
ConsoleInteractive . ConsoleReader . MessageReceived - = ConsoleReaderOnMessageReceived ;
2022-12-23 00:50:20 +08:00
ConsoleInteractive . ConsoleReader . OnInputChange - = ConsoleIO . AutocompleteHandler ;
ConsoleInteractive . ConsoleReader . StopReadThread ( ) ;
2022-12-21 14:01:05 +08:00
ConsoleIO . CancelAutocomplete ( ) ;
ConsoleIO . WriteLine ( string . Empty ) ;
2022-12-20 22:41:14 +08:00
}
2022-09-25 16:00:43 +02:00
/// <summary>
/// Register bots
/// </summary>
2022-12-23 00:50:20 +08:00
private static ChatBot [ ] GetChatbotsToRegister ( bool reload = false )
2022-12-21 14:01:05 +08:00
{
List < ChatBot > chatbotList = new ( ) ;
if ( Config . ChatBot . Alerts . Enabled ) { chatbotList . Add ( new Alerts ( ) ) ; }
if ( Config . ChatBot . AntiAFK . Enabled ) { chatbotList . Add ( new AntiAFK ( ) ) ; }
if ( Config . ChatBot . AutoAttack . Enabled ) { chatbotList . Add ( new AutoAttack ( ) ) ; }
if ( Config . ChatBot . AutoCraft . Enabled ) { chatbotList . Add ( new AutoCraft ( ) ) ; }
if ( Config . ChatBot . AutoDig . Enabled ) { chatbotList . Add ( new AutoDig ( ) ) ; }
if ( Config . ChatBot . AutoDrop . Enabled ) { chatbotList . Add ( new AutoDrop ( ) ) ; }
if ( Config . ChatBot . AutoEat . Enabled ) { chatbotList . Add ( new AutoEat ( ) ) ; }
if ( Config . ChatBot . AutoFishing . Enabled ) { chatbotList . Add ( new AutoFishing ( ) ) ; }
if ( Config . ChatBot . AutoRelog . Enabled ) { chatbotList . Add ( new AutoRelog ( ) ) ; }
if ( Config . ChatBot . AutoRespond . Enabled ) { chatbotList . Add ( new AutoRespond ( ) ) ; }
if ( Config . ChatBot . ChatLog . Enabled ) { chatbotList . Add ( new ChatLog ( ) ) ; }
if ( Config . ChatBot . DiscordBridge . Enabled ) { chatbotList . Add ( new DiscordBridge ( ) ) ; }
if ( Config . ChatBot . Farmer . Enabled ) { chatbotList . Add ( new Farmer ( ) ) ; }
if ( Config . ChatBot . FollowPlayer . Enabled ) { chatbotList . Add ( new FollowPlayer ( ) ) ; }
if ( Config . ChatBot . HangmanGame . Enabled ) { chatbotList . Add ( new HangmanGame ( ) ) ; }
if ( Config . ChatBot . Mailer . Enabled ) { chatbotList . Add ( new Mailer ( ) ) ; }
if ( Config . ChatBot . Map . Enabled ) { chatbotList . Add ( new Map ( ) ) ; }
if ( Config . ChatBot . PlayerListLogger . Enabled ) { chatbotList . Add ( new PlayerListLogger ( ) ) ; }
if ( Config . ChatBot . RemoteControl . Enabled ) { chatbotList . Add ( new RemoteControl ( ) ) ; }
// if (Config.ChatBot.ReplayCapture.Enabled && reload) { chatbotList.Add(new ReplayCapture()); }
if ( Config . ChatBot . ScriptScheduler . Enabled ) { chatbotList . Add ( new ScriptScheduler ( ) ) ; }
if ( Config . ChatBot . TelegramBridge . Enabled ) { chatbotList . Add ( new TelegramBridge ( ) ) ; }
2022-12-20 22:41:14 +08:00
// Add your ChatBot here by uncommenting and adapting
2022-12-23 00:50:20 +08:00
// chatbotList.Add(new ChatBots.YourBot());
chatbotList . Add ( new TestBot ( ) ) ;
2022-12-21 14:01:05 +08:00
return chatbotList . ToArray ( ) ;
2022-09-25 16:00:43 +02:00
}
2022-08-31 22:52:05 +08:00
/// <summary>
/// Retrieve messages from the queue and send.
/// Note: requires external locking.
/// </summary>
2022-12-20 22:41:14 +08:00
private async Task TrySendMessageToServer ( )
2022-08-31 22:52:05 +08:00
{
2022-12-21 14:01:05 +08:00
if ( handler ! = null )
2022-08-31 22:52:05 +08:00
{
2022-12-21 14:01:05 +08:00
while ( nextMessageSendTime < DateTime . Now & & chatQueue . TryDequeue ( out string? text ) )
{
await handler . SendChatMessage ( text , playerKeyPair ) ;
nextMessageSendTime = DateTime . Now + TimeSpan . FromSeconds ( Config . Main . Advanced . MessageCooldown ) ;
}
2022-08-31 22:52:05 +08:00
}
}
2020-05-26 11:20:12 +02:00
/// <summary>
2022-12-20 22:41:14 +08:00
/// Called ~20 times per second by the protocol handler
2020-05-26 11:20:12 +02:00
/// </summary>
2022-12-20 22:41:14 +08:00
public async Task OnUpdate ( )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
OldChatBotUpdateTrigger = ! OldChatBotUpdateTrigger ;
2022-12-21 14:01:05 +08:00
foreach ( ChatBot bot in chatbots )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
await bot . OnClientTickAsync ( ) ;
if ( OldChatBotUpdateTrigger )
2021-05-16 11:55:47 +02:00
{
2022-12-20 22:41:14 +08:00
try
{
bot . Update ( ) ;
bot . UpdateInternal ( ) ;
}
catch ( Exception e )
{
2022-12-23 00:50:20 +08:00
Log . Warn ( "Update: Got error from " + bot . ToString ( ) + ": " + e . ToString ( ) ) ;
2022-12-20 22:41:14 +08:00
}
2021-05-16 11:55:47 +02:00
}
2021-03-27 19:15:58 +01:00
}
2022-12-20 22:41:14 +08:00
await TrySendMessageToServer ( ) ;
2021-05-16 11:55:47 +02:00
if ( terrainAndMovementsEnabled & & locationReceived )
2021-03-27 19:15:58 +01:00
{
2022-12-20 22:41:14 +08:00
for ( int i = 0 ; i < Config . Main . Advanced . MovementSpeed / 2 ; i + + ) //Needs to run at 20 tps; MCC runs at 10 tps
2021-03-27 19:15:58 +01:00
{
2022-12-23 00:50:20 +08:00
await locationLock . WaitAsync ( ) ;
if ( _yaw = = null | | _pitch = = null )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
if ( steps ! = null & & steps . Count > 0 )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
location = steps . Dequeue ( ) ;
}
else if ( path ! = null & & path . Count > 0 )
{
Location next = path . Dequeue ( ) ;
steps = Movement . Move2Steps ( location , next , ref motionY ) ;
2021-10-29 06:45:30 +02:00
2022-12-23 00:50:20 +08:00
if ( Config . Main . Advanced . MoveHeadWhileWalking ) // Disable head movements to avoid anti-cheat triggers
UpdateLocation ( location , next + new Location ( 0 , 1 , 0 ) ) ; // Update yaw and pitch to look at next step
}
else
{
location = Movement . HandleGravity ( world , location , ref motionY ) ;
2020-05-26 11:20:12 +02:00
}
}
2022-12-23 00:50:20 +08:00
playerYaw = _yaw = = null ? playerYaw : _yaw . Value ;
playerPitch = _pitch = = null ? playerPitch : _pitch . Value ;
locationLock . Release ( ) ;
2022-12-20 22:41:14 +08:00
await handler ! . SendLocationUpdate ( location , Movement . IsOnGround ( world , location ) , _yaw , _pitch ) ;
2021-05-16 11:55:47 +02:00
}
2022-12-20 22:41:14 +08:00
// First 2 updates must be player position AND look, and player must not move (to conform with vanilla)
// Once yaw and pitch have been sent, switch back to location-only updates (without yaw and pitch)
_yaw = null ;
_pitch = null ;
2021-05-16 11:55:47 +02:00
}
2022-10-05 15:02:30 +08:00
if ( Config . Main . Advanced . AutoRespawn & & respawnTicks > 0 )
2021-05-16 11:55:47 +02:00
{
2022-12-20 22:41:14 +08:00
if ( - - respawnTicks = = 0 )
2022-12-23 00:50:20 +08:00
await SendRespawnPacketAsync ( ) ;
2020-05-26 11:20:12 +02:00
}
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . ClientTick , null ) ;
2020-05-26 11:20:12 +02:00
}
2021-05-16 11:55:47 +02:00
#region Connection Lost and Disconnect from Server
2020-05-26 11:20:12 +02:00
/// <summary>
/// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old.
/// </summary>
2022-12-20 22:41:14 +08:00
private async Task TimeoutDetector ( )
2020-05-26 11:20:12 +02:00
{
2021-04-30 12:28:27 +08:00
UpdateKeepAlive ( ) ;
2022-12-21 14:01:05 +08:00
using PeriodicTimer periodicTimer = new ( TimeSpan . FromSeconds ( Config . Main . Advanced . TcpTimeout ) ) ;
try
2020-05-26 11:20:12 +02:00
{
2022-12-21 14:01:05 +08:00
while ( await periodicTimer . WaitForNextTickAsync ( CancelTokenSource . Token ) & & ! CancelTokenSource . IsCancellationRequested )
2020-05-26 11:20:12 +02:00
{
2022-12-21 14:01:05 +08:00
if ( lastKeepAlive . AddSeconds ( Config . Main . Advanced . TcpTimeout ) < DateTime . Now )
{
OnConnectionLost ( ChatBot . DisconnectReason . ConnectionLost , Translations . error_timeout ) ;
return ;
}
2020-05-26 11:20:12 +02:00
}
}
2022-12-21 14:01:05 +08:00
catch ( AggregateException e )
{
if ( e . InnerException is not OperationCanceledException )
throw ;
}
catch ( OperationCanceledException ) { }
2020-05-26 11:20:12 +02:00
}
2021-04-30 12:28:27 +08:00
/// <summary>
/// Update last keep alive to current time
/// </summary>
private void UpdateKeepAlive ( )
{
2022-12-20 22:41:14 +08:00
lastKeepAlive = DateTime . Now ;
2021-04-30 12:28:27 +08:00
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Disconnect the client from the server (initiated from MCC)
/// </summary>
public void Disconnect ( )
{
2022-12-23 00:50:20 +08:00
for ( int i = 0 ; i < EventTypeCount ; + + i )
{
ChatbotEventTaskLocks [ i ] . Wait ( ) ;
WaitChatbotExecuteTask [ i ] . Wait ( ) ;
ChatbotEventTaskLocks [ i ] . Release ( ) ;
}
2022-12-21 14:01:05 +08:00
DispatchBotEvent ( bot = > bot . OnDisconnect ( ChatBot . DisconnectReason . UserLogout , string . Empty ) ) ;
2020-05-26 11:20:12 +02:00
2022-12-23 00:50:20 +08:00
TriggerEvent ( McClientEventType . ClientDisconnect ,
new Tuple < ChatBot . DisconnectReason , string > ( ChatBot . DisconnectReason . UserLogout , string . Empty ) ) . Wait ( ) ;
WaitChatbotExecuteTask [ ( int ) McClientEventType . ClientDisconnect ] . Wait ( ) ;
2022-12-21 14:01:05 +08:00
botsOnHold = chatbots ;
chatbots = Array . Empty < ChatBot > ( ) ;
2020-05-26 11:20:12 +02:00
if ( handler ! = null )
{
handler . Disconnect ( ) ;
handler . Dispose ( ) ;
}
2022-12-20 22:41:14 +08:00
tcpClient ? . Close ( ) ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// When connection has been lost, login was denied or played was kicked from the server
2020-05-26 11:20:12 +02:00
/// </summary>
2020-06-20 15:18:34 +02:00
public void OnConnectionLost ( ChatBot . DisconnectReason reason , string message )
2020-05-26 11:20:12 +02:00
{
2020-06-20 15:18:34 +02:00
switch ( reason )
{
case ChatBot . DisconnectReason . ConnectionLost :
2022-10-28 11:13:20 +08:00
message = Translations . mcc_disconnect_lost ;
2021-01-29 07:45:18 +08:00
Log . Info ( message ) ;
2020-06-20 15:18:34 +02:00
break ;
2020-05-26 11:20:12 +02:00
2020-06-20 15:18:34 +02:00
case ChatBot . DisconnectReason . InGameKick :
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . mcc_disconnect_server ) ;
2021-01-29 07:45:18 +08:00
Log . Info ( message ) ;
2020-06-20 15:18:34 +02:00
break ;
2020-05-26 11:20:12 +02:00
2020-06-20 15:18:34 +02:00
case ChatBot . DisconnectReason . LoginRejected :
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . mcc_disconnect_login ) ;
2021-01-29 07:45:18 +08:00
Log . Info ( message ) ;
2020-06-20 15:18:34 +02:00
break ;
case ChatBot . DisconnectReason . UserLogout :
2022-10-28 11:13:20 +08:00
throw new InvalidOperationException ( Translations . exception_user_logout ) ;
2020-06-14 15:45:20 +02:00
}
2022-12-20 22:41:14 +08:00
// Process AutoRelog last to make sure other bots can perform their cleanup tasks first (issue #1517)
2022-12-21 14:01:05 +08:00
List < ChatBot > onDisconnectBotList = chatbots . Where ( bot = > bot is not AutoRelog ) . ToList ( ) ;
onDisconnectBotList . AddRange ( chatbots . Where ( bot = > bot is AutoRelog ) ) ;
2021-04-18 18:44:02 +02:00
2022-12-20 22:41:14 +08:00
int restartDelay = - 1 ;
2021-04-18 18:44:02 +02:00
foreach ( ChatBot bot in onDisconnectBotList )
2020-05-26 11:20:12 +02:00
{
try
{
2022-12-20 22:41:14 +08:00
restartDelay = Math . Max ( restartDelay , bot . OnDisconnect ( reason , message ) ) ;
2020-05-26 11:20:12 +02:00
}
catch ( Exception e )
{
2022-10-02 18:31:08 +08:00
if ( e is not ThreadAbortException )
2020-05-26 11:20:12 +02:00
{
2021-01-29 07:45:18 +08:00
Log . Warn ( "OnDisconnect: Got error from " + bot . ToString ( ) + ": " + e . ToString ( ) ) ;
2020-05-26 11:20:12 +02:00
}
2022-12-20 22:41:14 +08:00
else throw ; // ThreadAbortException should not be caught
2020-05-26 11:20:12 +02:00
}
}
2022-12-20 22:41:14 +08:00
if ( restartDelay < 0 )
2022-12-21 14:01:05 +08:00
Program . SetExit ( handleFailure : true ) ;
2022-12-20 22:41:14 +08:00
else
2022-12-21 14:01:05 +08:00
Program . SetRestart ( restartDelay , true ) ;
2022-12-20 22:41:14 +08:00
handler ! . Dispose ( ) ;
world . Clear ( ) ;
2020-05-26 11:20:12 +02:00
}
2021-05-16 11:55:47 +02:00
#endregion
2022-12-23 00:50:20 +08:00
#region ChatBot event callback
private async Task TriggerEvent ( McClientEventType eventType , object? parameter )
{
int eventId = ( int ) eventType ;
Func < object? , Task > [ ] eventList = ChatbotEvents [ eventId ] ;
if ( eventList . Length > 0 )
{
await ChatbotEventTaskLocks [ eventId ] . WaitAsync ( ) ;
await WaitChatbotExecuteTask [ eventId ] ;
for ( int i = 0 ; i < eventList . Length ; + + i )
ChatbotEventTasks [ eventId ] [ i ] = eventList [ i ] ( parameter ) ;
WaitChatbotExecuteTask [ eventId ] = WaitTaskAndHandleException ( eventType ) ;
ChatbotEventTaskLocks [ eventId ] . Release ( ) ;
}
}
private async Task WaitTaskAndHandleException ( McClientEventType eventType )
{
Task [ ] taskList = ChatbotEventTasks [ ( int ) eventType ] ;
for ( int i = 0 ; i < taskList . Length ; + + i )
{
try
{
await taskList [ i ] ;
}
catch ( Exception exception )
{
Log . Error ( string . Format ( Translations . mcc_chatbot_event_exception , eventType . ToString ( ) , exception . ToString ( ) ) ) ;
}
}
}
private static async Task InitializeChatbotEventCallbacks ( IEnumerable < ChatBot > chatbotList )
{
List < Func < object? , Task > > [ ] tmpCallbackList = new List < Func < object? , Task > > [ EventTypeCount ] ;
for ( int i = 0 ; i < EventTypeCount ; + + i )
tmpCallbackList [ i ] = new ( ) ;
foreach ( ChatBot bot in chatbotList )
{
Tuple < McClientEventType , Func < object? , Task > > [ ] ? botEvents = bot . InitializeEventCallbacks ( ) ;
if ( botEvents ! = null )
{
ChatbotRegisteredEvents [ bot ] = new ( botEvents ) ;
foreach ( ( McClientEventType eventType , Func < object? , Task > callback ) in botEvents )
tmpCallbackList [ ( int ) eventType ] . Add ( callback ) ;
}
else
{
ChatbotRegisteredEvents [ bot ] = new ( ) ;
}
}
await EventCallbackWriteLock . WaitAsync ( ) ;
for ( int i = 0 ; i < EventTypeCount ; + + i )
{
ChatbotEvents [ i ] = tmpCallbackList [ i ] . ToArray ( ) ;
await UpdateChatbotEventTasksArray ( i ) ;
}
EventCallbackWriteLock . Release ( ) ;
}
/// <summary>
///
/// </summary>
/// <param name="bot"></param>
/// <param name="eventType"></param>
/// <param name="callback"></param>
/// <returns></returns>
public static async Task RegisterEventCallback ( ChatBot bot , McClientEventType eventType , Func < object? , Task > callback )
{
int eventId = ( int ) eventType ;
await EventCallbackWriteLock . WaitAsync ( ) ;
ChatbotEvents [ eventId ] = new List < Func < object? , Task > > ( ChatbotEvents [ eventId ] ) { callback } . ToArray ( ) ;
if ( ChatbotRegisteredEvents . TryGetValue ( bot , out var botEvents ) )
botEvents . Add ( new ( eventType , callback ) ) ;
else
ChatbotRegisteredEvents [ bot ] = new ( ) { new ( eventType , callback ) } ;
await UpdateChatbotEventTasksArray ( eventId ) ;
EventCallbackWriteLock . Release ( ) ;
}
/// <summary>
///
/// </summary>
/// <param name="bot"></param>
/// <param name="eventType"></param>
/// <param name="callback"></param>
/// <returns></returns>
public static async Task UnregisterEventCallback ( ChatBot bot , McClientEventType eventType , Func < object? , Task > callback )
{
int eventId = ( int ) eventType ;
await EventCallbackWriteLock . WaitAsync ( ) ;
List < Func < object? , Task > > newList = new ( ChatbotEvents [ eventId ] ) ;
newList . RemoveAll ( c = > c = = callback ) ;
ChatbotEvents [ eventId ] = newList . ToArray ( ) ;
if ( ChatbotRegisteredEvents . TryGetValue ( bot , out var botEvents ) )
botEvents . RemoveAll ( c = > c . Item1 = = eventType & & c . Item2 = = callback ) ;
await UpdateChatbotEventTasksArray ( eventId ) ;
EventCallbackWriteLock . Release ( ) ;
}
public static async Task UnregisterChatbotEventCallback ( ChatBot bot )
{
await EventCallbackWriteLock . WaitAsync ( ) ;
if ( ChatbotRegisteredEvents . TryGetValue ( bot , out var botEvents ) )
{
foreach ( ( McClientEventType eventType , Func < object? , Task > callback ) in botEvents )
{
int eventId = ( int ) eventType ;
List < Func < object? , Task > > newList = new ( ChatbotEvents [ eventId ] ) ;
newList . RemoveAll ( c = > c = = callback ) ;
ChatbotEvents [ eventId ] = newList . ToArray ( ) ;
await UpdateChatbotEventTasksArray ( eventId ) ;
}
ChatbotRegisteredEvents . Remove ( bot ) ;
}
EventCallbackWriteLock . Release ( ) ;
}
private static async Task UpdateChatbotEventTasksArray ( int eventId )
{
await ChatbotEventTaskLocks [ eventId ] . WaitAsync ( ) ;
await WaitChatbotExecuteTask [ eventId ] ;
ChatbotEventTasks [ eventId ] = new Task [ ChatbotEvents [ eventId ] . Length ] ;
ChatbotEventTaskLocks [ eventId ] . Release ( ) ;
}
#endregion
2021-05-16 11:55:47 +02:00
#region Command prompt and internal MCC commands
2022-08-15 23:55:44 +08:00
2022-12-23 00:50:20 +08:00
private void ConsoleReaderOnMessageReceived ( object? sender , string text )
2020-05-26 11:20:12 +02:00
{
2022-07-03 22:34:07 +08:00
2022-12-20 22:41:14 +08:00
if ( tcpClient ! . Client = = null )
2022-07-03 22:34:07 +08:00
return ;
2022-08-15 23:55:44 +08:00
2022-12-20 22:41:14 +08:00
if ( tcpClient . Client . Connected )
2022-12-23 00:50:20 +08:00
Task . Run ( async ( ) = > { await HandleCommandPromptText ( text ) ; } ) ;
2022-07-03 22:34:07 +08:00
else
return ;
2021-05-16 11:55:47 +02:00
}
2022-08-15 23:55:44 +08:00
2021-05-16 11:55:47 +02:00
/// <summary>
/// Allows the user to send chat messages, commands, and leave the server.
/// Process text from the MCC command prompt on the main thread.
/// </summary>
2022-12-20 22:41:14 +08:00
private async Task HandleCommandPromptText ( string text )
2021-05-16 11:55:47 +02:00
{
if ( ConsoleIO . BasicIO & & text . Length > 0 & & text [ 0 ] = = ( char ) 0x00 )
2020-10-24 17:41:35 +02:00
{
2021-05-16 11:55:47 +02:00
//Process a request from the GUI
2022-08-31 22:52:05 +08:00
string [ ] command = text [ 1. . ] . Split ( ( char ) 0x00 ) ;
2021-05-16 11:55:47 +02:00
switch ( command [ 0 ] . ToLower ( ) )
2020-10-24 17:41:35 +02:00
{
2021-05-16 11:55:47 +02:00
case "autocomplete" :
2022-12-20 22:41:14 +08:00
int id = await handler ! . AutoComplete ( command [ 1 ] ) ;
while ( ! ConsoleIO . AutoCompleteDone ) { await Task . Delay ( 100 ) ; }
2022-12-06 15:50:17 +08:00
if ( command . Length > 1 ) { ConsoleIO . WriteLine ( ( char ) 0x00 + "autocomplete" + ( char ) 0x00 + ConsoleIO . AutoCompleteResult ) ; }
2022-07-03 22:34:07 +08:00
else ConsoleIO . WriteLine ( ( char ) 0x00 + "autocomplete" + ( char ) 0x00 ) ;
2021-05-16 11:55:47 +02:00
break ;
2020-10-24 17:41:35 +02:00
}
}
2021-05-16 11:55:47 +02:00
else
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:55:47 +02:00
text = text . Trim ( ) ;
2022-12-06 15:50:17 +08:00
if ( text . Length > 1
& & Config . Main . Advanced . InternalCmdChar = = MainConfigHealper . MainConfig . AdvancedConfig . InternalCmdCharType . none
& & text [ 0 ] = = '/' )
{
2022-12-23 00:50:20 +08:00
await SendTextAsync ( text ) ;
2022-12-06 15:50:17 +08:00
}
else if ( text . Length > 2
& & Config . Main . Advanced . InternalCmdChar ! = MainConfigHealper . MainConfig . AdvancedConfig . InternalCmdCharType . none
& & text [ 0 ] = = Config . Main . Advanced . InternalCmdChar . ToChar ( )
& & text [ 1 ] = = '/' )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
await SendTextAsync ( text [ 1. . ] ) ;
2022-12-06 15:50:17 +08:00
}
else if ( text . Length > 0 )
{
if ( Config . Main . Advanced . InternalCmdChar = = MainConfigHealper . MainConfig . AdvancedConfig . InternalCmdCharType . none
| | text [ 0 ] = = Config . Main . Advanced . InternalCmdChar . ToChar ( ) )
2020-06-20 15:18:34 +02:00
{
2022-12-06 15:50:17 +08:00
CmdResult result = new ( ) ;
2022-10-05 15:02:30 +08:00
string command = Config . Main . Advanced . InternalCmdChar . ToChar ( ) = = ' ' ? text : text [ 1. . ] ;
2022-12-06 15:50:17 +08:00
if ( ! PerformInternalCommand ( Config . AppVar . ExpandVars ( command ) , ref result , Settings . Config . AppVar . GetVariables ( ) ) & & Config . Main . Advanced . InternalCmdChar . ToChar ( ) = = '/' )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
await SendTextAsync ( text ) ;
2021-05-16 11:55:47 +02:00
}
2022-12-06 15:50:17 +08:00
else if ( result . status ! = CmdResult . Status . NotRun & & ( result . status ! = CmdResult . Status . Done | | ! string . IsNullOrWhiteSpace ( result . result ) ) )
2021-05-16 11:55:47 +02:00
{
2022-12-06 15:50:17 +08:00
Log . Info ( result ) ;
2020-06-20 15:18:34 +02:00
}
}
2022-12-06 15:50:17 +08:00
else
{
2022-12-23 00:50:20 +08:00
await SendTextAsync ( text ) ;
2022-12-06 15:50:17 +08:00
}
2021-05-08 21:03:23 +08:00
}
}
2020-06-20 15:18:34 +02:00
}
2021-05-16 11:55:47 +02:00
/// <summary>
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary>
/// <param name="command">The command</param>
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
/// <param name="localVars">Local variables passed along with the command</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
2022-12-06 15:50:17 +08:00
public bool PerformInternalCommand ( string command , ref CmdResult result , Dictionary < string , object > ? localVars = null )
2021-05-16 11:55:47 +02:00
{
/* Process the provided command */
2022-12-06 15:50:17 +08:00
ParseResults < CmdResult > parse ;
2022-10-26 08:54:54 +08:00
try
2021-05-16 11:55:47 +02:00
{
2022-12-06 15:50:17 +08:00
parse = dispatcher . Parse ( command , result ) ;
2021-05-16 11:55:47 +02:00
}
2022-10-26 08:54:54 +08:00
catch ( Exception e )
2021-05-16 11:55:47 +02:00
{
2022-12-06 15:50:17 +08:00
Log . Debug ( "Parse fail: " + e . GetFullMessage ( ) ) ;
return false ;
2022-10-26 08:54:54 +08:00
}
2022-09-11 22:35:26 +02:00
2022-10-26 08:54:54 +08:00
try
{
2022-12-06 15:50:17 +08:00
dispatcher . Execute ( parse ) ;
2022-12-21 14:01:05 +08:00
foreach ( ChatBot bot in chatbots )
2022-12-06 15:50:17 +08:00
{
try
{
2022-12-21 14:01:05 +08:00
bot . OnInternalCommand ( command , string . Join ( ' ' , Command . GetArgs ( command ) ) , result ) ;
2022-12-06 15:50:17 +08:00
}
catch ( Exception e )
{
if ( e is not ThreadAbortException )
{
Log . Warn ( string . Format ( Translations . icmd_error , bot . ToString ( ) ? ? string . Empty , e . ToString ( ) ) ) ;
}
else throw ; //ThreadAbortException should not be caught
}
}
2022-10-26 08:54:54 +08:00
return true ;
2021-05-16 11:55:47 +02:00
}
2022-10-26 08:54:54 +08:00
catch ( CommandSyntaxException e )
2021-05-16 11:55:47 +02:00
{
2022-12-06 15:50:17 +08:00
if ( parse . Context . Nodes . Count = = 0 )
{
return false ;
}
else
{
Log . Info ( "§e" + e . Message ? ? e . StackTrace ? ? "Incorrect argument." ) ;
Log . Info ( dispatcher . GetAllUsageString ( parse . Context . Nodes [ 0 ] . Node . Name , false ) ) ;
return true ;
}
2021-05-16 11:55:47 +02:00
}
}
2022-09-25 16:00:43 +02:00
/// <summary>
/// Reload settings and bots
/// </summary>
/// <param name="hard">Marks if bots need to be hard reloaded</param>
2022-12-20 22:41:14 +08:00
public async Task ReloadSettings ( )
2022-09-25 16:00:43 +02:00
{
2022-11-05 20:27:10 +08:00
Program . ReloadSettings ( true ) ;
2022-12-20 22:41:14 +08:00
await ReloadBots ( ) ;
2022-09-25 16:00:43 +02:00
}
/// <summary>
/// Reload loaded bots (Only builtin bots)
/// </summary>
2022-12-20 22:41:14 +08:00
public async Task ReloadBots ( )
2022-09-25 16:00:43 +02:00
{
2022-12-20 22:41:14 +08:00
await UnloadAllBots ( ) ;
2022-09-25 16:00:43 +02:00
2022-12-23 00:50:20 +08:00
ChatBot [ ] bots = GetChatbotsToRegister ( true ) ;
2022-12-21 14:01:05 +08:00
foreach ( ChatBot bot in bots )
{
bot . SetHandler ( this ) ;
bot . Initialize ( ) ;
}
2022-12-23 00:50:20 +08:00
await InitializeChatbotEventCallbacks ( bots ) ;
if ( handler ! = null )
foreach ( ChatBot bot in bots )
bot . AfterGameJoined ( ) ;
2022-12-21 14:01:05 +08:00
chatbots = bots ;
2022-09-25 16:00:43 +02:00
}
/// <summary>
/// Unload All Bots
/// </summary>
2022-12-20 22:41:14 +08:00
public async Task UnloadAllBots ( )
2022-09-25 16:00:43 +02:00
{
2022-12-21 14:01:05 +08:00
foreach ( ChatBot bot in chatbots )
bot . OnUnload ( ) ;
chatbots = Array . Empty < ChatBot > ( ) ;
registeredBotPluginChannels . Clear ( ) ;
2022-12-23 00:50:20 +08:00
for ( int i = 0 ; i < ChatbotEvents . Length ; + + i )
ChatbotEvents [ i ] = Array . Empty < Func < object? , Task > > ( ) ;
ChatbotRegisteredEvents . Clear ( ) ;
2022-12-21 14:01:05 +08:00
await Task . CompletedTask ;
2021-05-08 21:03:23 +08:00
}
2021-05-16 11:55:47 +02:00
#endregion
2021-05-13 01:34:55 +08:00
2020-06-20 15:18:34 +02:00
#region Management : Load / Unload ChatBots and Enable / Disable settings
/// <summary>
/// Load a new bot
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task BotLoad ( ChatBot bot , bool init = true )
2020-06-20 15:18:34 +02:00
{
2022-12-21 14:01:05 +08:00
bot . SetHandler ( this ) ;
chatbots = new List < ChatBot > ( chatbots ) { bot } . ToArray ( ) ;
2021-05-16 11:48:52 +02:00
if ( init )
2022-12-23 00:50:20 +08:00
{
2022-12-21 14:01:05 +08:00
bot . Initialize ( ) ;
2022-12-23 00:50:20 +08:00
await InitializeChatbotEventCallbacks ( new ChatBot [ ] { bot } ) ;
}
2022-10-02 18:31:08 +08:00
if ( handler ! = null )
2022-12-21 14:01:05 +08:00
bot . AfterGameJoined ( ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Unload a bot
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task BotUnLoad ( ChatBot bot )
2020-06-20 15:18:34 +02:00
{
2022-12-21 14:01:05 +08:00
List < ChatBot > botList = new ( ) ;
2022-12-23 00:50:20 +08:00
botList . AddRange ( from botInList in chatbots
where ! ReferenceEquals ( botInList , bot )
select botInList ) ;
2022-12-21 14:01:05 +08:00
chatbots = botList . ToArray ( ) ;
2021-05-16 11:48:52 +02:00
2022-12-23 00:50:20 +08:00
bot . OnUnload ( ) ;
await UnregisterChatbotEventCallback ( bot ) ;
2021-05-16 11:48:52 +02:00
// ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon.
2022-12-23 00:50:20 +08:00
var botRegistrations = registeredBotPluginChannels . Where ( entry = > entry . Value . Contains ( bot ) ) . ToList ( ) ;
2021-05-16 11:48:52 +02:00
foreach ( var entry in botRegistrations )
{
2022-12-23 00:50:20 +08:00
await UnregisterPluginChannelAsync ( entry . Key , bot ) ;
2021-05-16 11:48:52 +02:00
}
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// Get Terrain and Movements status.
/// </summary>
public bool GetTerrainEnabled ( )
{
return terrainAndMovementsEnabled ;
}
/// <summary>
/// Get Inventory Handling Mode
/// </summary>
public bool GetInventoryEnabled ( )
{
return inventoryHandlingEnabled ;
}
2021-05-15 17:36:16 +02:00
/// <summary>
/// Get entity handling status
/// </summary>
/// <returns></returns>
/// <remarks>Entity Handling cannot be enabled in runtime (or after joining server)</remarks>
public bool GetEntityHandlingEnabled ( )
{
return entityHandlingEnabled ;
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Enable or disable Terrain and Movements.
/// Please note that Enabling will be deferred until next relog, respawn or world change.
/// </summary>
/// <param name="enabled">Enabled</param>
/// <returns>TRUE if the setting was applied immediately, FALSE if delayed.</returns>
public bool SetTerrainEnabled ( bool enabled )
{
2021-05-16 11:48:52 +02:00
if ( enabled )
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:48:52 +02:00
if ( ! terrainAndMovementsEnabled )
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:48:52 +02:00
terrainAndMovementsRequested = true ;
return false ;
2021-05-15 17:36:16 +02:00
}
2021-05-16 11:48:52 +02:00
}
else
{
terrainAndMovementsEnabled = false ;
terrainAndMovementsRequested = false ;
locationReceived = false ;
world . Clear ( ) ;
}
return true ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// Enable or disable Inventories.
/// Please note that Enabling will be deferred until next relog.
/// </summary>
/// <param name="enabled">Enabled</param>
/// <returns>TRUE if the setting was applied immediately, FALSE if delayed.</returns>
public bool SetInventoryEnabled ( bool enabled )
{
2021-05-16 11:48:52 +02:00
if ( enabled )
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:48:52 +02:00
if ( ! inventoryHandlingEnabled )
2021-05-15 17:36:16 +02:00
{
2021-05-16 11:48:52 +02:00
inventoryHandlingRequested = true ;
return false ;
2021-05-15 17:36:16 +02:00
}
2021-05-16 11:48:52 +02:00
}
else
{
inventoryHandlingEnabled = false ;
inventoryHandlingRequested = false ;
inventories . Clear ( ) ;
}
return true ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// Enable or disable Entity handling.
/// Please note that Enabling will be deferred until next relog.
/// </summary>
/// <param name="enabled">Enabled</param>
/// <returns>TRUE if the setting was applied immediately, FALSE if delayed.</returns>
public bool SetEntityHandlingEnabled ( bool enabled )
{
2021-05-16 11:48:52 +02:00
if ( ! enabled )
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:48:52 +02:00
if ( entityHandlingEnabled )
2020-05-26 11:20:12 +02:00
{
2021-05-16 11:48:52 +02:00
entityHandlingEnabled = false ;
return true ;
2020-05-26 11:20:12 +02:00
}
else
{
return false ;
}
2021-05-16 11:48:52 +02:00
}
else
{
// Entity Handling cannot be enabled in runtime (or after joining server)
return false ;
}
2020-05-26 11:20:12 +02:00
}
2020-09-07 03:51:42 +08:00
/// <summary>
/// Enable or disable network packet event calling.
/// </summary>
/// <remarks>
/// Enable this may increase memory usage.
/// </remarks>
/// <param name="enabled"></param>
public void SetNetworkPacketCaptureEnabled ( bool enabled )
{
2021-05-16 11:48:52 +02:00
networkPacketCaptureEnabled = enabled ;
2020-09-07 03:51:42 +08:00
}
2020-06-20 15:18:34 +02:00
#endregion
#region Getters : Retrieve data for use in other methods or ChatBots
Add Mailer bot (#1108)
* Update for the mail script.
As requested, I added the synchronization between the config debugmessage bool and the one in the script. Furthermore, I added a way to send anonymous mails by writing "tellonym". The messages are saved as normal mails, which lets them count to the mail cap, due to the fact that the host knows the sender.
I added the script to the chat bots because of the problems, that the scripts have with serialization. Instead of rewriting the whole serialization part, my idea was to add the script to the other chat bots, to avoid the compiling issues. Then the serialization would work perfectly fine. Then you could remove the option class at some point and move all the settings to the config file with the addition to activate the whole script.
* Correction of debug message loading.
The object was missing and the change would be overridden a few lines later.
* Update McClient.cs
* Add Mail to config file
* Correcting the safe file.
* Small correction of Settings.c
* Update Mailscript
Added a failsafe version of the path changing commands. If a path could not be found, an error will be created and the path will be reseted to standart, to avoid endless chains of errors.
* Fix for the mail script
Removed a wrong option call. Removed the debug_msg condition around the path functions. => Users are aware of what happened (if they see the error) although they turned off debug_msg.
* Added some features.
Added a try statement to all number changing commands. Added a command to list all moderators to the console.
* Serialization Fix
There was a chance, that if two bots work on one file, and two users send messages in a short time period, that one bot deserializes the message and then the other bot deserialize the same file, before the other one could save its changes. This would lead to one message disappearing, because one bot never deserialized this message. For this I changed the whole serialization process.
All changes are now committed after the interval and not after an incoming mail command directly. All mails are safed temporarily in cache and get serialized after the interval. Due to this changes, you can determit when the individual bot changes the file (there are no more direct interactions with the file after a command, which lead to a certain randomness). Furthermore you can now set an interval of e.g. 2 mins and reset the interval of one bot with "resettimer" after one minute so that the bots won't disturb eachother and no files get lost.
* My idea of a manual.
This is my idea of a manual for the bot. Improvements of my language / further ideas are welcome! :D
* addIgnored [NAME] and removeIgnored[NAME]
Added an ignored list. Moderators can add players to the list. The bot won't react to them and just log to the console that they are ignored, everytime they are sending a message, to ensure that they are not accidently ignored. (Just if debug_msg is active.)
Especially useful if there are other chat bots on the server, which spam many messages that aren't useful for the mail system. Or block spammers etc.
* Add the three commands to the manual.
Added addignored, removeignored and getignored to the manual.
* Remove moderators. Implement Console Control.
Due to security concerns, I converted all moderator commands to console internal commands. Thereby only the host can change crucial settings. Special thanks to ORelio for the hint!
* Added empty statement check
Added if to all commands, where the syntax is not already protected by a try, so that an incorrect syntax (Empty args[] due to missing statement) won't crash the script.
* Changed the serialization fail
If the programm can't safe the file, because of some strange character for instance, it first tries to change the path back to normal and if this not helps, it creates a new, file.
* toggle mail sending/receiving
Add an option to turn mailsending and the listening to commands in chat on/off.
* Updated manual.
- Removed moderator commands.
- Removed moderator part in the network manual
+ added the two new commands
+ added a waring for nick plugins and minecraft renames
+ added a small syntax example
* Updated the Settings.cs file.
* Smaller fixes and additions
+ improved command reading of 'mail' & 'tellonym'
+ sorted internal commands alphabetically
+ host can set a maximum message length
+ host can accept commands from public chat
+ host can decide if 'self mailing' (mailing yourself) is accepted
+ new order makes 'getsettings' easier to read
+ new internal commands to toggle 'publiccommands' and 'selfmailing' as well as the maximum mail size
- removed the old command interpreter
* Small improvements and additions
Added a few commands and settings
* Completing getsettings
+ added 'publiccomands'
* Completed getsettings
+ Added 'publiccommand' to 'getsettings'
* Removed single bolean, added Dictionary
- removed all boleans in the option class
- removed all functions relating them
+ added Dictionary for the booleans
+ added a single function to set/toggle all booleans
* Removed Commands, added interpreter
- Removed all Register commands
- removed all integer methods
+ added a single mail command
+ added integer dictionary
+ added integer handling similar like bool handling
* Small fix
+ Changed the numbers in several methods to adjust them to the new syntax.
- removed parameters in several methods, because they got unneccesary
* Even smaller fix
+ Sorted 'getsettings' alphabetically
+ corrected a typo
* New Serialization method.
Now serializing through the .INI format! Thanks to ORelio, who helped me a lot! :)
* Added different time
Added the option to switch between utc and the time of the local machine for timestamps.
* Made timeinutc serializable
Added the bool to the serialization method.
* Adding the INIFile.cs
For Dictionary serialization.
* Reworked ignore feature
Ignored players are now serialized in a file and reloaded, after the bot enters a server.
* Mailer bot refactoring
Rename Mail to Mailer
Move options to MinecraftClient.ini
Make the bot much simpler by removing some settings
Create specific MailDatabase and IgnoreList classes
However the core functionality for users is the same
Settings removed:
- allow_sendmail: Cannot use Mailer if it's disabled
- allow_receivemail: Cannot use Mailer if it's disabled
- path_setting: Settings moved to MinecraftClient.ini
- debug_msg: MCC already has a setting for that with LogDebugToConsole()
- auto_respawn: MCC already has a built-in auto-respawn feature
- allow_selfmail: Is it really necessary to block self mails? ;)
- maxcharsinmsg: Automatically calculated based on max chat message length
- timeinutc: DateTime is not show to the recipient so I think it's not absolutely necessary
- interval_sendmail: Set to 10 seconds for now
Internal Commands removed:
- changemailpath: Now a static setting in config
- changesettingspath: Now a static setting in config
- updatemails: Already updated every 10 seconds
- getsettings: Shown on startup with debugmessages=true
- resettimer: Seems only useful for debugging
- setbool: Settings are static in config file
- setinteger: Settings are static in config file
All user commands are retained:
- mail
- tellonym
* Reload database for mailer network feature
* Merge Mail documentation to Readme.md
Co-authored-by: ORelio <oreliogitantispam.l0gin@spamgourmet.com>
2020-08-03 21:44:39 +02:00
/// <summary>
/// Get max length for chat messages
/// </summary>
/// <returns>Max length, in characters</returns>
public int GetMaxChatMessageLength ( )
{
2022-12-20 22:41:14 +08:00
return handler ! . GetMaxChatMessageLength ( ) ;
Add Mailer bot (#1108)
* Update for the mail script.
As requested, I added the synchronization between the config debugmessage bool and the one in the script. Furthermore, I added a way to send anonymous mails by writing "tellonym". The messages are saved as normal mails, which lets them count to the mail cap, due to the fact that the host knows the sender.
I added the script to the chat bots because of the problems, that the scripts have with serialization. Instead of rewriting the whole serialization part, my idea was to add the script to the other chat bots, to avoid the compiling issues. Then the serialization would work perfectly fine. Then you could remove the option class at some point and move all the settings to the config file with the addition to activate the whole script.
* Correction of debug message loading.
The object was missing and the change would be overridden a few lines later.
* Update McClient.cs
* Add Mail to config file
* Correcting the safe file.
* Small correction of Settings.c
* Update Mailscript
Added a failsafe version of the path changing commands. If a path could not be found, an error will be created and the path will be reseted to standart, to avoid endless chains of errors.
* Fix for the mail script
Removed a wrong option call. Removed the debug_msg condition around the path functions. => Users are aware of what happened (if they see the error) although they turned off debug_msg.
* Added some features.
Added a try statement to all number changing commands. Added a command to list all moderators to the console.
* Serialization Fix
There was a chance, that if two bots work on one file, and two users send messages in a short time period, that one bot deserializes the message and then the other bot deserialize the same file, before the other one could save its changes. This would lead to one message disappearing, because one bot never deserialized this message. For this I changed the whole serialization process.
All changes are now committed after the interval and not after an incoming mail command directly. All mails are safed temporarily in cache and get serialized after the interval. Due to this changes, you can determit when the individual bot changes the file (there are no more direct interactions with the file after a command, which lead to a certain randomness). Furthermore you can now set an interval of e.g. 2 mins and reset the interval of one bot with "resettimer" after one minute so that the bots won't disturb eachother and no files get lost.
* My idea of a manual.
This is my idea of a manual for the bot. Improvements of my language / further ideas are welcome! :D
* addIgnored [NAME] and removeIgnored[NAME]
Added an ignored list. Moderators can add players to the list. The bot won't react to them and just log to the console that they are ignored, everytime they are sending a message, to ensure that they are not accidently ignored. (Just if debug_msg is active.)
Especially useful if there are other chat bots on the server, which spam many messages that aren't useful for the mail system. Or block spammers etc.
* Add the three commands to the manual.
Added addignored, removeignored and getignored to the manual.
* Remove moderators. Implement Console Control.
Due to security concerns, I converted all moderator commands to console internal commands. Thereby only the host can change crucial settings. Special thanks to ORelio for the hint!
* Added empty statement check
Added if to all commands, where the syntax is not already protected by a try, so that an incorrect syntax (Empty args[] due to missing statement) won't crash the script.
* Changed the serialization fail
If the programm can't safe the file, because of some strange character for instance, it first tries to change the path back to normal and if this not helps, it creates a new, file.
* toggle mail sending/receiving
Add an option to turn mailsending and the listening to commands in chat on/off.
* Updated manual.
- Removed moderator commands.
- Removed moderator part in the network manual
+ added the two new commands
+ added a waring for nick plugins and minecraft renames
+ added a small syntax example
* Updated the Settings.cs file.
* Smaller fixes and additions
+ improved command reading of 'mail' & 'tellonym'
+ sorted internal commands alphabetically
+ host can set a maximum message length
+ host can accept commands from public chat
+ host can decide if 'self mailing' (mailing yourself) is accepted
+ new order makes 'getsettings' easier to read
+ new internal commands to toggle 'publiccommands' and 'selfmailing' as well as the maximum mail size
- removed the old command interpreter
* Small improvements and additions
Added a few commands and settings
* Completing getsettings
+ added 'publiccomands'
* Completed getsettings
+ Added 'publiccommand' to 'getsettings'
* Removed single bolean, added Dictionary
- removed all boleans in the option class
- removed all functions relating them
+ added Dictionary for the booleans
+ added a single function to set/toggle all booleans
* Removed Commands, added interpreter
- Removed all Register commands
- removed all integer methods
+ added a single mail command
+ added integer dictionary
+ added integer handling similar like bool handling
* Small fix
+ Changed the numbers in several methods to adjust them to the new syntax.
- removed parameters in several methods, because they got unneccesary
* Even smaller fix
+ Sorted 'getsettings' alphabetically
+ corrected a typo
* New Serialization method.
Now serializing through the .INI format! Thanks to ORelio, who helped me a lot! :)
* Added different time
Added the option to switch between utc and the time of the local machine for timestamps.
* Made timeinutc serializable
Added the bool to the serialization method.
* Adding the INIFile.cs
For Dictionary serialization.
* Reworked ignore feature
Ignored players are now serialized in a file and reloaded, after the bot enters a server.
* Mailer bot refactoring
Rename Mail to Mailer
Move options to MinecraftClient.ini
Make the bot much simpler by removing some settings
Create specific MailDatabase and IgnoreList classes
However the core functionality for users is the same
Settings removed:
- allow_sendmail: Cannot use Mailer if it's disabled
- allow_receivemail: Cannot use Mailer if it's disabled
- path_setting: Settings moved to MinecraftClient.ini
- debug_msg: MCC already has a setting for that with LogDebugToConsole()
- auto_respawn: MCC already has a built-in auto-respawn feature
- allow_selfmail: Is it really necessary to block self mails? ;)
- maxcharsinmsg: Automatically calculated based on max chat message length
- timeinutc: DateTime is not show to the recipient so I think it's not absolutely necessary
- interval_sendmail: Set to 10 seconds for now
Internal Commands removed:
- changemailpath: Now a static setting in config
- changesettingspath: Now a static setting in config
- updatemails: Already updated every 10 seconds
- getsettings: Shown on startup with debugmessages=true
- resettimer: Seems only useful for debugging
- setbool: Settings are static in config file
- setinteger: Settings are static in config file
All user commands are retained:
- mail
- tellonym
* Reload database for mailer network feature
* Merge Mail documentation to Readme.md
Co-authored-by: ORelio <oreliogitantispam.l0gin@spamgourmet.com>
2020-08-03 21:44:39 +02:00
}
2021-05-16 11:55:47 +02:00
/// <summary>
/// Get a list of disallowed characters in chat
/// </summary>
/// <returns></returns>
public static char [ ] GetDisallowedChatCharacters ( )
{
return new char [ ] { ( char ) 167 , ( char ) 127 } ; // Minecraft color code and ASCII code DEL
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Get all inventories. ID 0 is the player inventory.
/// </summary>
/// <returns>All inventories</returns>
public Dictionary < int , Container > GetInventories ( )
{
return inventories ;
}
2021-07-04 11:26:41 +05:00
2022-10-17 15:14:55 +02:00
/// <summary>
/// Get all Entities
/// </summary>
/// <returns>Ladt Enchantments</returns>
public EnchantmentData ? GetLastEnchantments ( )
{
return lastEnchantment ;
}
2020-07-04 13:45:51 +05:00
/// <summary>
2020-08-29 17:46:04 +05:00
/// Get all Entities
2020-07-04 13:45:51 +05:00
/// </summary>
/// <returns>All Entities</returns>
public Dictionary < int , Entity > GetEntities ( )
{
return entities ;
}
2020-05-26 11:20:12 +02:00
2020-08-29 17:46:04 +05:00
/// <summary>
/// Get all players latency
/// </summary>
/// <returns>All players latency</returns>
2021-07-04 11:26:41 +05:00
public Dictionary < string , int > GetPlayersLatency ( )
{
2022-08-15 23:55:44 +08:00
Dictionary < string , int > playersLatency = new ( ) ;
foreach ( var player in onlinePlayers )
playersLatency . Add ( player . Value . Name , player . Value . Ping ) ;
2021-07-04 11:26:41 +05:00
return playersLatency ;
2020-08-29 17:46:04 +05:00
}
2021-07-04 11:26:41 +05:00
2020-05-26 11:20:12 +02:00
/// <summary>
/// Get client player's inventory items
/// </summary>
/// <param name="inventoryID">Window ID of the requested inventory</param>
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
2022-09-08 14:04:23 +08:00
public Container ? GetInventory ( int inventoryID )
2020-05-26 11:20:12 +02:00
{
2022-12-11 13:00:19 +08:00
if ( inventories . TryGetValue ( inventoryID , out Container ? inv ) )
return inv ;
else
return null ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// Get client player's inventory items
/// </summary>
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
public Container GetPlayerInventory ( )
{
2022-09-08 14:04:23 +08:00
return GetInventory ( 0 ) ! ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// Get a set of online player names
2020-05-26 11:20:12 +02:00
/// </summary>
2020-06-20 15:18:34 +02:00
/// <returns>Online player names</returns>
public string [ ] GetOnlinePlayers ( )
2020-05-26 11:20:12 +02:00
{
2020-06-20 15:18:34 +02:00
lock ( onlinePlayers )
2020-05-26 11:20:12 +02:00
{
2022-08-15 23:55:44 +08:00
string [ ] playerNames = new string [ onlinePlayers . Count ] ;
int idx = 0 ;
foreach ( var player in onlinePlayers )
playerNames [ idx + + ] = player . Value . Name ;
return playerNames ;
2020-05-26 11:20:12 +02:00
}
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// Get a dictionary of online player names and their corresponding UUID
2020-05-26 11:20:12 +02:00
/// </summary>
2020-06-20 15:18:34 +02:00
/// <returns>Dictionay of online players, key is UUID, value is Player name</returns>
public Dictionary < string , string > GetOnlinePlayersWithUUID ( )
2020-05-26 11:20:12 +02:00
{
2022-10-02 18:31:08 +08:00
Dictionary < string , string > uuid2Player = new ( ) ;
2020-06-20 15:18:34 +02:00
lock ( onlinePlayers )
{
foreach ( Guid key in onlinePlayers . Keys )
{
2022-08-15 23:55:44 +08:00
uuid2Player . Add ( key . ToString ( ) , onlinePlayers [ key ] . Name ) ;
2020-06-20 15:18:34 +02:00
}
}
return uuid2Player ;
2020-05-26 11:20:12 +02:00
}
2022-08-15 23:55:44 +08:00
/// <summary>
/// Get player info from uuid
/// </summary>
/// <param name="uuid">Player's UUID</param>
/// <returns>Player info</returns>
public PlayerInfo ? GetPlayerInfo ( Guid uuid )
{
lock ( onlinePlayers )
{
2022-12-20 22:41:14 +08:00
if ( onlinePlayers . TryGetValue ( uuid , out PlayerInfo ? playerInfo ) )
return playerInfo ;
2022-08-15 23:55:44 +08:00
else
return null ;
}
}
2020-06-20 15:18:34 +02:00
#endregion
2020-05-26 11:20:12 +02:00
2020-06-20 15:18:34 +02:00
#region Action methods : Perform an action on the Server
2020-05-26 11:20:12 +02:00
/// <summary>
2020-06-20 15:18:34 +02:00
/// Move to the specified location
2020-05-26 11:20:12 +02:00
/// </summary>
2022-09-05 22:03:47 +08:00
/// <param name="goal">Location to reach</param>
2020-06-20 15:18:34 +02:00
/// <param name="allowUnsafe">Allow possible but unsafe locations thay may hurt the player: lava, cactus...</param>
2020-07-04 11:12:23 +02:00
/// <param name="allowDirectTeleport">Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins</param>
2022-04-29 22:56:41 +00:00
/// <param name="maxOffset">If no valid path can be found, also allow locations within specified distance of destination</param>
/// <param name="minOffset">Do not get closer of destination than specified distance</param>
/// <param name="timeout">How long to wait until the path is evaluated (default: 5 seconds)</param>
/// <remarks>When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset</remarks>
2020-06-20 15:18:34 +02:00
/// <returns>True if a path has been found</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > MoveToAsync ( Location goal , bool allowUnsafe = false , bool allowDirectTeleport = false , int maxOffset = 0 , int minOffset = 0 , TimeSpan ? timeout = null )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
if ( allowDirectTeleport )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
await locationLock . WaitAsync ( ) ;
// 1-step path to the desired location without checking anything
UpdateLocation ( goal , goal ) ; // Update yaw and pitch to look at next step
await handler . SendLocationUpdate ( goal , Movement . IsOnGround ( world , goal ) , _yaw , _pitch ) ;
locationLock . Release ( ) ;
return true ;
}
else
{
// Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps
path = await Movement . CalculatePath ( world , location , goal , allowUnsafe , maxOffset , minOffset , timeout ? ? TimeSpan . FromSeconds ( 5 ) ) ;
return path ! = null ;
2020-06-20 15:18:34 +02:00
}
}
/// <summary>
/// Send a chat message or command to the server
/// </summary>
/// <param name="text">Text to send to the server</param>
2022-12-23 00:50:20 +08:00
public async Task SendTextAsync ( string text )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return ;
2022-12-20 22:41:14 +08:00
if ( string . IsNullOrEmpty ( text ) )
2022-09-02 09:29:24 +08:00
return ;
2022-12-20 22:41:14 +08:00
int maxLength = handler ! . GetMaxChatMessageLength ( ) ;
2022-09-02 09:29:24 +08:00
2022-12-20 22:41:14 +08:00
if ( text . Length > maxLength ) //Message is too long?
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
if ( text [ 0 ] = = '/' )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
//Send the first 100/256 chars of the command
text = text [ . . maxLength ] ;
chatQueue . Enqueue ( text ) ;
2020-06-20 15:18:34 +02:00
}
2022-09-02 09:29:24 +08:00
else
2022-12-20 22:41:14 +08:00
{
//Split the message into several messages
while ( text . Length > maxLength )
{
chatQueue . Enqueue ( text [ . . maxLength ] ) ;
text = text [ maxLength . . ] ;
}
2022-12-23 00:50:20 +08:00
if ( ! string . IsNullOrEmpty ( text ) )
chatQueue . Enqueue ( text ) ;
2022-12-20 22:41:14 +08:00
}
2020-06-20 15:18:34 +02:00
}
2022-12-20 22:41:14 +08:00
else
2022-12-23 00:50:20 +08:00
{
2022-12-20 22:41:14 +08:00
chatQueue . Enqueue ( text ) ;
2022-12-23 00:50:20 +08:00
}
2022-12-20 22:41:14 +08:00
await TrySendMessageToServer ( ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Allow to respawn after death
/// </summary>
/// <returns>True if packet successfully sent</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > SendRespawnPacketAsync ( )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendRespawnPacket ( ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Registers the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to register.</param>
/// <param name="bot">The bot to register the channel for.</param>
2022-12-23 00:50:20 +08:00
public async Task RegisterPluginChannelAsync ( string channel , ChatBot bot )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
if ( registeredBotPluginChannels . TryGetValue ( channel , out List < ChatBot > ? channelList ) )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
channelList . Add ( bot ) ;
2021-05-16 11:48:52 +02:00
}
else
{
2022-12-23 00:50:20 +08:00
List < ChatBot > bots = new ( ) { bot } ;
2021-05-16 11:48:52 +02:00
registeredBotPluginChannels [ channel ] = bots ;
2022-12-23 00:50:20 +08:00
await SendPluginChannelMessageAsync ( "REGISTER" , Encoding . UTF8 . GetBytes ( channel ) , true ) ;
2021-05-16 11:48:52 +02:00
}
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Unregisters the given plugin channel for the given bot.
/// </summary>
/// <param name="channel">The channel to unregister.</param>
/// <param name="bot">The bot to unregister the channel for.</param>
2022-12-23 00:50:20 +08:00
public async Task UnregisterPluginChannelAsync ( string channel , ChatBot bot )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
if ( registeredBotPluginChannels . TryGetValue ( channel , out List < ChatBot > ? channelList ) )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
List < ChatBot > registeredBots = channelList ;
registeredBots . RemoveAll ( item = > ReferenceEquals ( item , bot ) ) ;
2021-05-16 11:48:52 +02:00
if ( registeredBots . Count = = 0 )
2020-06-20 15:18:34 +02:00
{
2021-05-16 11:48:52 +02:00
registeredBotPluginChannels . Remove ( channel ) ;
2022-12-23 00:50:20 +08:00
await SendPluginChannelMessageAsync ( "UNREGISTER" , Encoding . UTF8 . GetBytes ( channel ) , true ) ;
2020-06-20 15:18:34 +02:00
}
2021-05-16 11:48:52 +02:00
}
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information
/// about plugin channels.
/// </summary>
/// <param name="channel">The channel to send the packet on.</param>
/// <param name="data">The payload for the packet.</param>
/// <param name="sendEvenIfNotRegistered">Whether the packet should be sent even if the server or the client hasn't registered it yet.</param>
/// <returns>Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > SendPluginChannelMessageAsync ( string channel , byte [ ] data , bool sendEvenIfNotRegistered = false )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2021-05-16 11:48:52 +02:00
if ( ! sendEvenIfNotRegistered )
2020-06-20 15:18:34 +02:00
{
2021-05-16 11:48:52 +02:00
if ( ! registeredBotPluginChannels . ContainsKey ( channel ) )
2020-06-20 15:18:34 +02:00
{
2021-05-16 11:48:52 +02:00
return false ;
}
if ( ! registeredServerPluginChannels . Contains ( channel ) )
{
return false ;
2020-06-20 15:18:34 +02:00
}
2021-05-16 11:48:52 +02:00
}
2022-12-23 00:50:20 +08:00
return await handler . SendPluginChannelPacket ( channel , data ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Send the Entity Action packet with the Specified ID
/// </summary>
/// <returns>TRUE if the item was successfully used</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > SendEntityActionAsync ( EntityActionType entityAction )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendEntityAction ( playerEntityID , ( int ) entityAction ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Use the item currently in the player's hand
/// </summary>
/// <returns>TRUE if the item was successfully used</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > UseItemOnHandAsync ( )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendUseItem ( 0 , sequenceId ) ;
2020-06-20 15:18:34 +02:00
}
2022-09-12 02:19:20 +08:00
/// <summary>
/// Use the item currently in the player's left hand
/// </summary>
/// <returns>TRUE if the item was successfully used</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > UseItemOnOffHandAsync ( )
2022-09-12 02:19:20 +08:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendUseItem ( 1 , sequenceId ) ;
2022-09-12 02:19:20 +08:00
}
2022-09-08 14:04:23 +08:00
/// <summary>
/// Try to merge a slot
/// </summary>
/// <param name="inventory">The container where the item is located</param>
/// <param name="item">Items to be processed</param>
/// <param name="slotId">The ID of the slot of the item to be processed</param>
/// <param name="curItem">The slot that was put down</param>
/// <param name="curId">The ID of the slot being put down</param>
/// <param name="changedSlots">Record changes</param>
/// <returns>Whether to fully merge</returns>
private static bool TryMergeSlot ( Container inventory , Item item , int slotId , Item curItem , int curId , List < Tuple < short , Item ? > > changedSlots )
{
int spaceLeft = curItem . Type . StackCount ( ) - curItem . Count ;
if ( curItem . Type = = item ! . Type & & spaceLeft > 0 )
{
// Put item on that stack
if ( item . Count < = spaceLeft )
{
// Can fit into the stack
item . Count = 0 ;
curItem . Count + = item . Count ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) curId , curItem ) ) ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
inventory . Items . Remove ( slotId ) ;
return true ;
}
else
{
item . Count - = spaceLeft ;
curItem . Count + = spaceLeft ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) curId , curItem ) ) ;
}
}
return false ;
}
/// <summary>
/// Store items in a new slot
/// </summary>
/// <param name="inventory">The container where the item is located</param>
/// <param name="item">Items to be processed</param>
/// <param name="slotId">The ID of the slot of the item to be processed</param>
/// <param name="newSlotId">ID of the new slot</param>
/// <param name="changedSlots">Record changes</param>
private static void StoreInNewSlot ( Container inventory , Item item , int slotId , int newSlotId , List < Tuple < short , Item ? > > changedSlots )
{
Item newItem = new ( item . Type , item . Count , item . NBT ) ;
inventory . Items [ newSlotId ] = newItem ;
inventory . Items . Remove ( slotId ) ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) newSlotId , newItem ) ) ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
}
2020-06-20 15:18:34 +02:00
/// <summary>
/// Click a slot in the specified window
/// </summary>
/// <returns>TRUE if the slot was successfully clicked</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > DoWindowActionAsync ( int windowId , int slotId , WindowActionType action )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2022-09-08 14:04:23 +08:00
Item ? item = null ;
List < Tuple < short , Item ? > > changedSlots = new ( ) ; // List<Slot ID, Changed Items>
2022-12-20 22:41:14 +08:00
lock ( inventoryLock )
2021-05-16 11:48:52 +02:00
{
2022-12-20 22:41:14 +08:00
if ( inventories . TryGetValue ( windowId , out Container ? container ) )
container . Items . TryGetValue ( slotId , out item ) ;
2021-05-15 17:36:16 +02:00
2022-12-20 22:41:14 +08:00
// Update our inventory base on action type
Container inventory = GetInventory ( windowId ) ! ;
Container playerInventory = GetInventory ( 0 ) ! ;
if ( inventory ! = null )
{
switch ( action )
{
case WindowActionType . LeftClick :
// Check if cursor have item (slot -1)
if ( playerInventory . Items . ContainsKey ( - 1 ) )
2021-05-16 11:48:52 +02:00
{
2022-12-20 22:41:14 +08:00
// When item on cursor and clicking slot 0, nothing will happen
if ( slotId = = 0 ) break ;
// Check target slot also have item?
if ( inventory . Items . ContainsKey ( slotId ) )
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
// Check if both item are the same?
if ( inventory . Items [ slotId ] . Type = = playerInventory . Items [ - 1 ] . Type )
2020-07-21 16:22:45 +08:00
{
2022-12-20 22:41:14 +08:00
int maxCount = inventory . Items [ slotId ] . Type . StackCount ( ) ;
// Check item stacking
if ( ( inventory . Items [ slotId ] . Count + playerInventory . Items [ - 1 ] . Count ) < = maxCount )
{
// Put cursor item to target
inventory . Items [ slotId ] . Count + = playerInventory . Items [ - 1 ] . Count ;
playerInventory . Items . Remove ( - 1 ) ;
}
else
{
// Leave some item on cursor
playerInventory . Items [ - 1 ] . Count - = ( maxCount - inventory . Items [ slotId ] . Count ) ;
inventory . Items [ slotId ] . Count = maxCount ;
}
2020-07-21 16:22:45 +08:00
}
else
{
2022-12-20 22:41:14 +08:00
// Swap two items
( inventory . Items [ slotId ] , playerInventory . Items [ - 1 ] ) = ( playerInventory . Items [ - 1 ] , inventory . Items [ slotId ] ) ;
2020-07-21 16:22:45 +08:00
}
2020-07-05 15:48:19 +08:00
}
else
{
2022-12-20 22:41:14 +08:00
// Put cursor item to target
inventory . Items [ slotId ] = playerInventory . Items [ - 1 ] ;
playerInventory . Items . Remove ( - 1 ) ;
2020-07-05 15:48:19 +08:00
}
2022-07-25 01:13:41 +08:00
2022-12-20 22:41:14 +08:00
if ( inventory . Items . TryGetValue ( slotId , out Item ? item1 ) )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , item1 ) ) ;
else
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
}
2022-07-25 17:22:01 +08:00
else
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
// Check target slot have item?
if ( inventory . Items . ContainsKey ( slotId ) )
{
// When taking item from slot 0, server will update us
if ( slotId = = 0 ) break ;
2020-07-07 11:26:49 +08:00
2022-12-20 22:41:14 +08:00
// Put target slot item to cursor
playerInventory . Items [ - 1 ] = inventory . Items [ slotId ] ;
inventory . Items . Remove ( slotId ) ;
2022-07-25 01:13:41 +08:00
2022-12-20 22:41:14 +08:00
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
}
2021-05-16 11:48:52 +02:00
}
2022-12-20 22:41:14 +08:00
break ;
case WindowActionType . RightClick :
// Check if cursor have item (slot -1)
if ( playerInventory . Items . TryGetValue ( - 1 , out Item ? playerItem ) )
2021-05-16 11:48:52 +02:00
{
2022-12-20 22:41:14 +08:00
// When item on cursor and clicking slot 0, nothing will happen
if ( slotId = = 0 ) break ;
// Check target slot have item?
if ( inventory . Items . TryGetValue ( slotId , out Item ? invItem ) )
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
// Check if both item are the same?
if ( invItem . Type = = playerItem . Type )
2020-07-21 16:22:45 +08:00
{
2022-12-20 22:41:14 +08:00
// Check item stacking
if ( invItem . Count < invItem . Type . StackCount ( ) )
{
// Drop 1 item count from cursor
playerItem . Count - - ;
invItem . Count + + ;
}
}
else
{
// Swap two items
( invItem , playerItem ) = ( playerItem , invItem ) ;
2020-07-21 16:22:45 +08:00
}
2020-07-05 15:48:19 +08:00
}
else
{
2022-12-20 22:41:14 +08:00
// Drop 1 item count from cursor
inventory . Items [ slotId ] = new ( playerItem . Type , 1 , playerItem . NBT ) ;
playerItem . Count - - ;
2020-07-05 15:48:19 +08:00
}
}
else
{
2022-12-20 22:41:14 +08:00
// Check target slot have item?
if ( inventory . Items . ContainsKey ( slotId ) )
2021-05-16 11:48:52 +02:00
{
2022-12-20 22:41:14 +08:00
if ( slotId = = 0 )
{
// no matter how many item in slot 0, only 1 will be taken out
// Also server will update us
break ;
}
if ( inventory . Items [ slotId ] . Count = = 1 )
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
// Only 1 item count. Put it to cursor
playerInventory . Items [ - 1 ] = inventory . Items [ slotId ] ;
inventory . Items . Remove ( slotId ) ;
2020-07-05 15:48:19 +08:00
}
2021-05-16 11:48:52 +02:00
else
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
// Take half of the item stack to cursor
if ( inventory . Items [ slotId ] . Count % 2 = = 0 )
{
// Can be evenly divided
Item itemTmp = inventory . Items [ slotId ] ;
playerInventory . Items [ - 1 ] = new Item ( itemTmp . Type , itemTmp . Count / 2 , itemTmp . NBT ) ;
inventory . Items [ slotId ] . Count = itemTmp . Count / 2 ;
}
else
{
// Cannot be evenly divided. item count on cursor is always larger than item on inventory
Item itemTmp = inventory . Items [ slotId ] ;
playerInventory . Items [ - 1 ] = new Item ( itemTmp . Type , ( itemTmp . Count + 1 ) / 2 , itemTmp . NBT ) ;
inventory . Items [ slotId ] . Count = ( itemTmp . Count - 1 ) / 2 ;
}
2020-07-05 15:48:19 +08:00
}
2021-05-16 11:48:52 +02:00
}
}
2022-12-20 22:41:14 +08:00
if ( inventory . Items . TryGetValue ( slotId , out Item ? item2 ) )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , item2 ) ) ;
else
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
break ;
case WindowActionType . ShiftClick :
if ( slotId = = 0 ) break ;
if ( item ! = null )
{
/* Target slot have item */
2021-05-16 11:48:52 +02:00
2022-12-20 22:41:14 +08:00
bool lower2upper = false , upper2backpack = false , backpack2hotbar = false ; // mutual exclusion
bool hotbarFirst = true ; // Used when upper2backpack = true
int upperStartSlot = 9 ;
int upperEndSlot = 35 ;
int lowerStartSlot = 36 ;
2021-05-16 11:48:52 +02:00
2022-12-20 22:41:14 +08:00
switch ( inventory . Type )
{
case ContainerType . PlayerInventory :
if ( slotId > = 0 & & slotId < = 8 | | slotId = = 45 )
{
if ( slotId ! = 0 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 9 ;
}
else if ( item ! = null & & false /* Check if wearable */ )
{
lower2upper = true ;
// upperStartSlot = ?;
// upperEndSlot = ?;
// Todo: Distinguish the type of equipment
}
else
{
if ( slotId > = 9 & & slotId < = 35 )
{
backpack2hotbar = true ;
lowerStartSlot = 36 ;
}
else
{
lower2upper = true ;
upperStartSlot = 9 ;
upperEndSlot = 35 ;
}
}
break ;
case ContainerType . Generic_9x1 :
if ( slotId > = 0 & & slotId < = 8 )
{
upper2backpack = true ;
lowerStartSlot = 9 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 8 ;
}
break ;
case ContainerType . Generic_9x2 :
if ( slotId > = 0 & & slotId < = 17 )
{
upper2backpack = true ;
lowerStartSlot = 18 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 17 ;
}
break ;
case ContainerType . Generic_9x3 :
case ContainerType . ShulkerBox :
if ( slotId > = 0 & & slotId < = 26 )
{
upper2backpack = true ;
lowerStartSlot = 27 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 26 ;
}
break ;
case ContainerType . Generic_9x4 :
if ( slotId > = 0 & & slotId < = 35 )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
upper2backpack = true ;
2022-09-08 14:04:23 +08:00
lowerStartSlot = 36 ;
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
2022-09-08 14:04:23 +08:00
upperEndSlot = 35 ;
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Generic_9x5 :
if ( slotId > = 0 & & slotId < = 44 )
{
upper2backpack = true ;
lowerStartSlot = 45 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 44 ;
}
break ;
case ContainerType . Generic_9x6 :
if ( slotId > = 0 & & slotId < = 53 )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
upper2backpack = true ;
lowerStartSlot = 54 ;
2022-09-08 14:04:23 +08:00
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 53 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Generic_3x3 :
if ( slotId > = 0 & & slotId < = 8 )
{
upper2backpack = true ;
lowerStartSlot = 9 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 8 ;
}
break ;
case ContainerType . Anvil :
if ( slotId > = 0 & & slotId < = 2 )
{
if ( slotId > = 0 & & slotId < = 1 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 3 ;
}
else
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 1 ;
}
break ;
case ContainerType . Beacon :
if ( slotId = = 0 )
{
2022-09-08 14:04:23 +08:00
hotbarFirst = false ;
2022-12-20 22:41:14 +08:00
upper2backpack = true ;
lowerStartSlot = 1 ;
}
else if ( item ! = null & & item . Count = = 1 & & ( item . Type = = ItemType . NetheriteIngot | |
item . Type = = ItemType . Emerald | | item . Type = = ItemType . Diamond | | item . Type = = ItemType . GoldIngot | |
item . Type = = ItemType . IronIngot ) & & ! inventory . Items . ContainsKey ( 0 ) )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 0 ;
2022-09-08 14:04:23 +08:00
}
else
2022-12-20 22:41:14 +08:00
{
if ( slotId > = 1 & & slotId < = 27 )
{
backpack2hotbar = true ;
lowerStartSlot = 28 ;
}
else
{
lower2upper = true ;
upperStartSlot = 1 ;
upperEndSlot = 27 ;
}
}
break ;
case ContainerType . BlastFurnace :
case ContainerType . Furnace :
case ContainerType . Smoker :
if ( slotId > = 0 & & slotId < = 2 )
{
if ( slotId > = 0 & & slotId < = 1 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 3 ;
}
else if ( item ! = null & & false /* Check if it can be burned */ )
2022-09-08 14:04:23 +08:00
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 0 ;
2022-09-08 14:04:23 +08:00
}
else
2022-12-20 22:41:14 +08:00
{
if ( slotId > = 3 & & slotId < = 29 )
{
backpack2hotbar = true ;
lowerStartSlot = 30 ;
}
else
{
lower2upper = true ;
upperStartSlot = 3 ;
upperEndSlot = 29 ;
}
}
break ;
case ContainerType . BrewingStand :
if ( slotId > = 0 & & slotId < = 3 )
{
upper2backpack = true ;
lowerStartSlot = 5 ;
}
else if ( item ! = null & & item . Type = = ItemType . BlazePowder )
{
lower2upper = true ;
if ( ! inventory . Items . ContainsKey ( 4 ) | | inventory . Items [ 4 ] . Count < 64 )
upperStartSlot = upperEndSlot = 4 ;
else
upperStartSlot = upperEndSlot = 3 ;
}
else if ( item ! = null & & false /* Check if it can be used for alchemy */ )
{
lower2upper = true ;
2022-09-08 14:04:23 +08:00
upperStartSlot = upperEndSlot = 3 ;
2022-12-20 22:41:14 +08:00
}
else if ( item ! = null & & ( item . Type = = ItemType . Potion | | item . Type = = ItemType . GlassBottle ) )
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 2 ;
}
else
{
if ( slotId > = 5 & & slotId < = 31 )
{
backpack2hotbar = true ;
lowerStartSlot = 32 ;
}
else
{
lower2upper = true ;
upperStartSlot = 5 ;
upperEndSlot = 31 ;
}
}
break ;
case ContainerType . Crafting :
if ( slotId > = 0 & & slotId < = 9 )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
if ( slotId > = 1 & & slotId < = 9 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 10 ;
2022-09-08 14:04:23 +08:00
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 1 ;
upperEndSlot = 9 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Enchantment :
2022-09-08 14:04:23 +08:00
if ( slotId > = 0 & & slotId < = 1 )
{
2022-12-20 22:41:14 +08:00
upper2backpack = true ;
lowerStartSlot = 5 ;
}
else if ( item ! = null & & item . Type = = ItemType . LapisLazuli )
{
lower2upper = true ;
upperStartSlot = upperEndSlot = 1 ;
2022-09-08 14:04:23 +08:00
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 0 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Grindstone :
if ( slotId > = 0 & & slotId < = 2 )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
if ( slotId > = 0 & & slotId < = 1 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 3 ;
}
else if ( item ! = null & & false /* Check */ )
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 1 ;
2022-09-08 14:04:23 +08:00
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 1 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Hopper :
if ( slotId > = 0 & & slotId < = 4 )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
upper2backpack = true ;
lowerStartSlot = 5 ;
2022-09-08 14:04:23 +08:00
}
else
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 4 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
break ;
case ContainerType . Lectern :
return false ;
// break;
case ContainerType . Loom :
if ( slotId > = 0 & & slotId < = 3 )
{
if ( slotId > = 0 & & slotId < = 5 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 4 ;
}
else if ( item ! = null & & false /* Check for availability for staining */ )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
lower2upper = true ;
// upperStartSlot = ?;
// upperEndSlot = ?;
2022-09-08 14:04:23 +08:00
}
else
2022-12-20 22:41:14 +08:00
{
if ( slotId > = 4 & & slotId < = 30 )
{
backpack2hotbar = true ;
lowerStartSlot = 31 ;
}
else
{
lower2upper = true ;
upperStartSlot = 4 ;
upperEndSlot = 30 ;
}
}
break ;
case ContainerType . Merchant :
if ( slotId > = 0 & & slotId < = 2 )
{
if ( slotId > = 0 & & slotId < = 1 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 3 ;
}
else if ( item ! = null & & false /* Check if it is available for trading */ )
2022-09-08 14:04:23 +08:00
{
lower2upper = true ;
2022-12-20 22:41:14 +08:00
upperStartSlot = 0 ;
upperEndSlot = 1 ;
2022-09-08 14:04:23 +08:00
}
2022-12-20 22:41:14 +08:00
else
{
if ( slotId > = 3 & & slotId < = 29 )
{
backpack2hotbar = true ;
lowerStartSlot = 30 ;
}
else
{
lower2upper = true ;
upperStartSlot = 3 ;
upperEndSlot = 29 ;
}
}
break ;
case ContainerType . Cartography :
if ( slotId > = 0 & & slotId < = 2 )
{
if ( slotId > = 0 & & slotId < = 1 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 3 ;
}
else if ( item ! = null & & item . Type = = ItemType . FilledMap )
{
lower2upper = true ;
upperStartSlot = upperEndSlot = 0 ;
}
else if ( item ! = null & & item . Type = = ItemType . Map )
{
lower2upper = true ;
upperStartSlot = upperEndSlot = 1 ;
}
else
{
if ( slotId > = 3 & & slotId < = 29 )
{
backpack2hotbar = true ;
lowerStartSlot = 30 ;
}
else
{
lower2upper = true ;
upperStartSlot = 3 ;
upperEndSlot = 29 ;
}
}
break ;
case ContainerType . Stonecutter :
if ( slotId > = 0 & & slotId < = 1 )
{
if ( slotId = = 0 )
hotbarFirst = false ;
upper2backpack = true ;
lowerStartSlot = 2 ;
}
else if ( item ! = null & & false /* Check if it is available for stone cutteing */ )
{
lower2upper = true ;
upperStartSlot = 0 ;
upperEndSlot = 0 ;
}
else
{
if ( slotId > = 2 & & slotId < = 28 )
{
backpack2hotbar = true ;
lowerStartSlot = 29 ;
}
else
{
lower2upper = true ;
upperStartSlot = 2 ;
upperEndSlot = 28 ;
}
}
break ;
// TODO: Define more container type here
default :
return false ;
2020-07-21 16:22:45 +08:00
}
2022-12-20 22:41:14 +08:00
// Cursor have item or not doesn't matter
// If hotbar already have same item, will put on it first until every stack are full
// If no more same item , will put on the first empty slot (smaller slot id)
// If inventory full, item will not move
int itemCount = inventory . Items [ slotId ] . Count ;
if ( lower2upper )
2020-07-21 16:22:45 +08:00
{
2022-12-20 22:41:14 +08:00
int firstEmptySlot = - 1 ;
for ( int i = upperStartSlot ; i < = upperEndSlot ; + + i )
2020-07-21 16:22:45 +08:00
{
2022-09-08 14:04:23 +08:00
if ( inventory . Items . TryGetValue ( i , out Item ? curItem ) )
2020-07-21 16:22:45 +08:00
{
2022-09-08 14:04:23 +08:00
if ( TryMergeSlot ( inventory , item ! , slotId , curItem , i , changedSlots ) )
break ;
2020-07-21 16:22:45 +08:00
}
2022-12-20 22:41:14 +08:00
else if ( firstEmptySlot = = - 1 )
firstEmptySlot = i ;
2022-09-08 14:04:23 +08:00
}
if ( item ! . Count > 0 )
{
2022-12-20 22:41:14 +08:00
if ( firstEmptySlot ! = - 1 )
StoreInNewSlot ( inventory , item , slotId , firstEmptySlot , changedSlots ) ;
2022-09-08 14:04:23 +08:00
else if ( item . Count ! = itemCount )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , inventory . Items [ slotId ] ) ) ;
2020-07-09 14:30:24 +08:00
}
2020-07-21 16:22:45 +08:00
}
2022-12-20 22:41:14 +08:00
else if ( upper2backpack )
{
int hotbarEnd = lowerStartSlot + 4 * 9 - 1 ;
if ( hotbarFirst )
{
int lastEmptySlot = - 1 ;
for ( int i = hotbarEnd ; i > = lowerStartSlot ; - - i )
{
if ( inventory . Items . TryGetValue ( i , out Item ? curItem ) )
{
if ( TryMergeSlot ( inventory , item ! , slotId , curItem , i , changedSlots ) )
break ;
}
else if ( lastEmptySlot = = - 1 )
lastEmptySlot = i ;
}
if ( item ! . Count > 0 )
{
if ( lastEmptySlot ! = - 1 )
StoreInNewSlot ( inventory , item , slotId , lastEmptySlot , changedSlots ) ;
else if ( item . Count ! = itemCount )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , inventory . Items [ slotId ] ) ) ;
}
}
else
{
int firstEmptySlot = - 1 ;
for ( int i = lowerStartSlot ; i < = hotbarEnd ; + + i )
{
if ( inventory . Items . TryGetValue ( i , out Item ? curItem ) )
{
if ( TryMergeSlot ( inventory , item ! , slotId , curItem , i , changedSlots ) )
break ;
}
else if ( firstEmptySlot = = - 1 )
firstEmptySlot = i ;
}
if ( item ! . Count > 0 )
{
if ( firstEmptySlot ! = - 1 )
StoreInNewSlot ( inventory , item , slotId , firstEmptySlot , changedSlots ) ;
else if ( item . Count ! = itemCount )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , inventory . Items [ slotId ] ) ) ;
}
}
}
else if ( backpack2hotbar )
2020-07-21 16:22:45 +08:00
{
2022-12-20 22:41:14 +08:00
int hotbarEnd = lowerStartSlot + 1 * 9 - 1 ;
2022-09-08 14:04:23 +08:00
int firstEmptySlot = - 1 ;
for ( int i = lowerStartSlot ; i < = hotbarEnd ; + + i )
2020-07-21 16:22:45 +08:00
{
2022-09-08 14:04:23 +08:00
if ( inventory . Items . TryGetValue ( i , out Item ? curItem ) )
{
if ( TryMergeSlot ( inventory , item ! , slotId , curItem , i , changedSlots ) )
break ;
}
else if ( firstEmptySlot = = - 1 )
firstEmptySlot = i ;
2020-07-21 16:22:45 +08:00
}
2022-09-08 14:04:23 +08:00
if ( item ! . Count > 0 )
2020-07-21 16:22:45 +08:00
{
2022-09-08 14:04:23 +08:00
if ( firstEmptySlot ! = - 1 )
StoreInNewSlot ( inventory , item , slotId , firstEmptySlot , changedSlots ) ;
else if ( item . Count ! = itemCount )
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , inventory . Items [ slotId ] ) ) ;
2022-07-25 01:13:41 +08:00
}
2022-09-08 14:04:23 +08:00
}
}
2022-12-20 22:41:14 +08:00
break ;
case WindowActionType . DropItem :
if ( inventory . Items . TryGetValue ( slotId , out Item ? item3 ) )
2022-09-08 14:04:23 +08:00
{
2022-12-20 22:41:14 +08:00
item3 . Count - - ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , inventory . Items [ slotId ] ) ) ;
}
2022-09-08 14:04:23 +08:00
2022-12-20 22:41:14 +08:00
if ( inventory . Items [ slotId ] . Count < = 0 )
{
inventory . Items . Remove ( slotId ) ;
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
2020-07-09 14:30:24 +08:00
}
2021-05-15 17:36:16 +02:00
2022-12-20 22:41:14 +08:00
break ;
case WindowActionType . DropItemStack :
2020-07-05 15:48:19 +08:00
inventory . Items . Remove ( slotId ) ;
2022-09-08 14:04:23 +08:00
changedSlots . Add ( new Tuple < short , Item ? > ( ( short ) slotId , null ) ) ;
2022-12-20 22:41:14 +08:00
break ;
}
2020-07-05 15:48:19 +08:00
}
2021-05-16 11:48:52 +02:00
}
2022-12-20 22:41:14 +08:00
return await handler ! . SendWindowAction ( windowId , slotId , action , item , changedSlots , inventories [ windowId ] . StateID ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Give Creative Mode items into regular/survival Player Inventory
/// </summary>
/// <remarks>(obviously) requires to be in creative mode</remarks>
/// <param name="slot">Destination inventory slot</param>
/// <param name="itemType">Item type</param>
/// <param name="count">Item count</param>
2020-06-20 21:30:23 +02:00
/// <param name="nbt">Item NBT</param>
2020-06-20 15:18:34 +02:00
/// <returns>TRUE if item given successfully</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > DoCreativeGiveAsync ( int slot , ItemType itemType , int count , Dictionary < string , object > ? nbt = null )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendCreativeInventoryAction ( slot , itemType , count , nbt ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Plays animation (Player arm swing)
/// </summary>
/// <param name="animation">0 for left arm, 1 for right arm</param>
/// <returns>TRUE if animation successfully done</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > DoAnimationAsync ( int animation )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendAnimation ( animation , playerEntityID ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Close the specified inventory window
/// </summary>
/// <param name="windowId">Window ID</param>
/// <returns>TRUE if the window was successfully closed</returns>
2020-07-09 22:21:39 +08:00
/// <remarks>Sending close window for inventory 0 can cause server to update our inventory if there are any item in the crafting area</remarks>
2022-12-23 00:50:20 +08:00
public async Task < bool > CloseInventoryAsync ( int windowId )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2022-12-20 22:41:14 +08:00
bool needCloseWindow = false ;
lock ( inventoryLock )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
if ( inventories . ContainsKey ( windowId ) )
{
if ( windowId ! = 0 )
inventories . Remove ( windowId ) ;
needCloseWindow = true ;
}
2021-05-16 11:48:52 +02:00
}
2022-12-20 22:41:14 +08:00
if ( needCloseWindow )
2022-12-23 00:50:20 +08:00
return await handler . SendCloseWindow ( windowId ) ;
2022-12-20 22:41:14 +08:00
else
return false ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Clean all inventory
/// </summary>
/// <returns>TRUE if the uccessfully clear</returns>
public bool ClearInventories ( )
{
2021-05-15 17:36:16 +02:00
if ( ! inventoryHandlingEnabled )
return false ;
2021-05-16 11:48:52 +02:00
2022-12-20 22:41:14 +08:00
lock ( inventoryLock )
{
inventories . Clear ( ) ;
2022-12-23 00:50:20 +08:00
inventories [ 0 ] = new Container ( 0 , ContainerType . PlayerInventory , Translations . cmd_inventory_player_inventory ) ;
2022-12-20 22:41:14 +08:00
}
2021-05-16 11:48:52 +02:00
return true ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Interact with an entity
/// </summary>
/// <param name="EntityID"></param>
2022-08-15 17:31:17 -04:00
/// <param name="type">Type of interaction (interact, attack...)</param>
2020-07-04 13:45:51 +05:00
/// <param name="hand">Hand.MainHand or Hand.OffHand</param>
2020-06-20 15:18:34 +02:00
/// <returns>TRUE if interaction succeeded</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > InteractEntityAsync ( int entityID , InteractType type , Hand hand = Hand . MainHand )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2021-05-16 11:48:52 +02:00
if ( entities . ContainsKey ( entityID ) )
2020-07-04 13:45:51 +05:00
{
2022-08-15 17:31:17 -04:00
if ( type = = InteractType . Interact )
2020-07-04 13:45:51 +05:00
{
2022-12-23 00:50:20 +08:00
return await handler . SendInteractEntity ( entityID , ( int ) type , ( int ) hand ) ;
2021-05-16 11:48:52 +02:00
}
else
{
2022-12-23 00:50:20 +08:00
return await handler . SendInteractEntity ( entityID , ( int ) type ) ;
2020-07-04 13:45:51 +05:00
}
2021-05-16 11:48:52 +02:00
}
else return false ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Place the block at hand in the Minecraft world
/// </summary>
/// <param name="location">Location to place block to</param>
2020-06-20 16:07:54 +02:00
/// <param name="blockFace">Block face (e.g. Direction.Down when clicking on the block below to place this block)</param>
2020-06-20 15:18:34 +02:00
/// <returns>TRUE if successfully placed</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > PlaceBlockAsync ( Location location , Direction blockFace , Hand hand = Hand . MainHand )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SendPlayerBlockPlacement ( ( int ) hand , location , blockFace , sequenceId ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
2020-06-20 21:30:23 +02:00
/// Attempt to dig a block at the specified location
2020-06-20 15:18:34 +02:00
/// </summary>
2020-06-20 16:07:54 +02:00
/// <param name="location">Location of block to dig</param>
2020-09-13 16:33:25 +02:00
/// <param name="swingArms">Also perform the "arm swing" animation</param>
2020-11-14 10:19:51 +01:00
/// <param name="lookAtBlock">Also look at the block before digging</param>
2022-12-23 00:50:20 +08:00
public async Task < bool > DigBlockAsync ( Location location , bool swingArms = true , bool lookAtBlock = true )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2021-05-15 17:36:16 +02:00
if ( ! GetTerrainEnabled ( ) )
return false ;
2021-05-16 11:48:52 +02:00
// TODO select best face from current player location
Direction blockFace = Direction . Down ;
// Look at block before attempting to break it
if ( lookAtBlock )
UpdateLocation ( GetCurrentLocation ( ) , location ) ;
// Send dig start and dig end, will need to wait for server response to know dig result
// See https://wiki.vg/How_to_Write_a_Client#Digging for more details
2022-12-23 00:50:20 +08:00
return ( await handler . SendPlayerDigging ( 0 , location , blockFace , sequenceId ) )
& & ( ! swingArms | | await DoAnimationAsync ( ( int ) Hand . MainHand ) )
& & await handler . SendPlayerDigging ( 2 , location , blockFace , sequenceId ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Change active slot in the player inventory
/// </summary>
/// <param name="slot">Slot to activate (0 to 8)</param>
/// <returns>TRUE if the slot was changed</returns>
2022-12-23 00:50:20 +08:00
public async Task < bool > ChangeSlotAsync ( short slot )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2021-05-16 11:48:52 +02:00
if ( slot < 0 | | slot > 8 )
return false ;
CurrentSlot = Convert . ToByte ( slot ) ;
2022-12-23 00:50:20 +08:00
return await handler . SendHeldItemChange ( slot ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Update sign text
/// </summary>
2020-06-20 21:30:23 +02:00
/// <param name="location">sign location</param>
/// <param name="line1">text one</param>
/// <param name="line2">text two</param>
/// <param name="line3">text three</param>
/// <param name="line4">text1 four</param>
2022-12-23 00:50:20 +08:00
public async Task < bool > UpdateSignAsync ( Location location , string line1 , string line2 , string line3 , string line4 )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2020-06-20 21:30:23 +02:00
// TODO Open sign editor first https://wiki.vg/Protocol#Open_Sign_Editor
2022-12-23 00:50:20 +08:00
return await handler . SendUpdateSign ( location , line1 , line2 , line3 , line4 ) ;
2020-06-20 15:18:34 +02:00
}
2020-11-08 23:39:07 +01:00
/// <summary>
/// Select villager trade
/// </summary>
/// <param name="selectedSlot">The slot of the trade, starts at 0.</param>
2022-12-23 00:50:20 +08:00
public async Task < bool > SelectTradeAsync ( int selectedSlot )
2020-11-08 23:39:07 +01:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . SelectTrade ( selectedSlot ) ;
2020-11-08 23:39:07 +01:00
}
2021-07-04 11:26:41 +05:00
2020-07-04 13:45:51 +05:00
/// <summary>
/// Update command block
/// </summary>
/// <param name="location">command block location</param>
/// <param name="command">command</param>
/// <param name="mode">command block mode</param>
/// <param name="flags">command block flags</param>
2022-12-23 00:50:20 +08:00
public async Task < bool > UpdateCommandBlockAsync ( Location location , string command , CommandBlockMode mode , CommandBlockFlags flags )
2020-07-04 13:45:51 +05:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
return await handler . UpdateCommandBlock ( location , command , mode , flags ) ;
2020-07-04 13:45:51 +05:00
}
2021-11-17 17:33:52 +01:00
/// <summary>
/// Teleport to player in spectator mode
/// </summary>
/// <param name="entity">Player to teleport to</param>
/// Teleporting to other entityies is NOT implemented yet
2022-12-23 00:50:20 +08:00
public async Task < bool > SpectateAsync ( Entity entity )
2021-11-17 17:33:52 +01:00
{
2022-08-15 23:55:44 +08:00
if ( entity . Type = = EntityType . Player )
2021-11-17 17:33:52 +01:00
{
2022-12-23 00:50:20 +08:00
return await SpectateByUuidAsync ( entity . UUID ) ;
2021-11-17 17:33:52 +01:00
}
else
{
return false ;
}
}
/// <summary>
/// Teleport to player/entity in spectator mode
/// </summary>
/// <param name="UUID">UUID of player/entity to teleport to</param>
2022-12-23 00:50:20 +08:00
public async Task < bool > SpectateByUuidAsync ( Guid UUID )
2021-11-17 17:33:52 +01:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return false ;
2022-08-15 23:55:44 +08:00
if ( GetGamemode ( ) = = 3 )
2021-11-17 17:33:52 +01:00
{
2022-12-23 00:50:20 +08:00
return await handler . SendSpectate ( UUID ) ;
2021-11-17 17:33:52 +01:00
}
else
{
return false ;
}
}
2020-06-20 15:18:34 +02:00
#endregion
#region Event handlers : An event occurs on the Server
/// <summary>
2020-06-20 15:39:16 +02:00
/// Dispatch a ChatBot event with automatic exception handling
2020-06-20 15:18:34 +02:00
/// </summary>
2020-06-20 15:39:16 +02:00
/// <example>
/// Example for calling SomeEvent() on all bots at once:
/// DispatchBotEvent(bot => bot.SomeEvent());
/// </example>
/// <param name="action">Action to execute on each bot</param>
/// <param name="botList">Only fire the event for the specified bot list (default: all bots)</param>
2022-08-15 23:55:44 +08:00
private void DispatchBotEvent ( Action < ChatBot > action , IEnumerable < ChatBot > ? botList = null )
2020-06-20 15:18:34 +02:00
{
2022-12-21 14:01:05 +08:00
botList ? ? = chatbots ;
foreach ( ChatBot bot in botList )
2020-06-20 15:18:34 +02:00
{
try
{
2020-06-20 15:39:16 +02:00
action ( bot ) ;
2020-06-20 15:18:34 +02:00
}
catch ( Exception e )
{
2022-10-02 18:31:08 +08:00
if ( e is not ThreadAbortException )
2020-06-20 15:18:34 +02:00
{
2022-12-20 22:41:14 +08:00
// Retrieve parent method name to determine which event caused the exception
2022-10-02 18:31:08 +08:00
System . Diagnostics . StackFrame frame = new ( 1 ) ;
System . Reflection . MethodBase method = frame . GetMethod ( ) ! ;
2020-06-20 15:39:16 +02:00
string parentMethodName = method . Name ;
2022-12-20 22:41:14 +08:00
// Display a meaningful error message to help debugging the ChatBot
2021-01-29 07:45:18 +08:00
Log . Error ( parentMethodName + ": Got error from " + bot . ToString ( ) + ": " + e . ToString ( ) ) ;
2020-06-20 15:18:34 +02:00
}
2022-12-20 22:41:14 +08:00
else throw ; // ThreadAbortException should not be caught here as in can happen when disconnecting from server
2020-06-20 15:18:34 +02:00
}
}
2020-06-20 15:39:16 +02:00
}
2020-09-07 03:51:42 +08:00
/// <summary>
/// Called when a network packet received or sent
/// </summary>
/// <remarks>
/// Only called if <see cref="networkPacketEventEnabled"/> is set to True
/// </remarks>
/// <param name="packetID">Packet ID</param>
/// <param name="packetData">A copy of Packet Data</param>
/// <param name="isLogin">The packet is login phase or playing phase</param>
/// <param name="isInbound">The packet is received from server or sent by client</param>
2022-12-23 00:50:20 +08:00
public async Task OnNetworkPacketAsync ( int packetID , byte [ ] packetData , bool isLogin , bool isInbound )
2020-09-07 03:51:42 +08:00
{
2022-12-23 00:50:20 +08:00
if ( networkPacketCaptureEnabled )
{
await TriggerEvent ( McClientEventType . NetworkPacket ,
new Tuple < int , byte [ ] , bool , bool > ( packetID , packetData , isLogin , isInbound ) ) ;
DispatchBotEvent ( bot = > bot . OnNetworkPacket ( packetID , new ( packetData ) , isLogin , isInbound ) ) ;
}
2020-09-07 03:51:42 +08:00
}
2020-06-20 15:39:16 +02:00
/// <summary>
/// Called when a server was successfully joined
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnGameJoinedAsync ( )
2020-06-20 15:39:16 +02:00
{
2022-12-23 00:50:20 +08:00
if ( handler = = null )
return ;
2022-10-05 15:02:30 +08:00
string? bandString = Config . Main . Advanced . BrandInfo . ToBrandString ( ) ;
2022-12-23 00:50:20 +08:00
if ( ! string . IsNullOrWhiteSpace ( bandString ) )
await handler . SendBrandInfo ( bandString . Trim ( ) ) ;
2020-06-20 15:39:16 +02:00
2022-10-05 15:02:30 +08:00
if ( Config . MCSettings . Enabled )
2022-12-23 00:50:20 +08:00
await handler . SendClientSettings (
2022-10-05 15:02:30 +08:00
Config . MCSettings . Locale ,
Config . MCSettings . RenderDistance ,
( byte ) Config . MCSettings . Difficulty ,
( byte ) Config . MCSettings . ChatMode ,
Config . MCSettings . ChatColors ,
Config . MCSettings . Skin . GetByte ( ) ,
( byte ) Config . MCSettings . MainHand ) ;
2020-06-20 15:39:16 +02:00
2020-06-20 15:18:34 +02:00
if ( inventoryHandlingRequested )
{
inventoryHandlingRequested = false ;
inventoryHandlingEnabled = true ;
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . extra_inventory_enabled ) ;
2020-06-20 15:18:34 +02:00
}
2020-06-20 15:39:16 +02:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . GameJoin , null ) ;
2020-08-29 23:53:29 +08:00
DispatchBotEvent ( bot = > bot . AfterGameJoined ( ) ) ;
2022-12-06 15:50:17 +08:00
2022-12-20 22:41:14 +08:00
await ConsoleIO . InitCommandList ( dispatcher ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Called when the player respawns, which happens on login, respawn and world change.
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnRespawnAsync ( )
2020-06-20 15:18:34 +02:00
{
if ( terrainAndMovementsRequested )
{
terrainAndMovementsEnabled = true ;
terrainAndMovementsRequested = false ;
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . extra_terrainandmovement_enabled ) ;
2020-06-20 15:18:34 +02:00
}
if ( terrainAndMovementsEnabled )
{
world . Clear ( ) ;
}
2020-06-20 15:39:16 +02:00
2020-08-20 21:36:50 +05:00
entities . Clear ( ) ;
2020-06-20 15:39:16 +02:00
ClearInventories ( ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . Respawn , null ) ;
2020-08-07 13:35:23 +05:00
DispatchBotEvent ( bot = > bot . OnRespawn ( ) ) ;
2020-06-20 15:18:34 +02:00
}
2022-04-29 22:56:41 +00:00
/// <summary>
/// Check if the client is currently processing a Movement.
/// </summary>
/// <returns>true if a movement is currently handled</returns>
2022-08-15 23:55:44 +08:00
public bool ClientIsMoving ( )
2022-04-29 22:56:41 +00:00
{
return terrainAndMovementsEnabled & & locationReceived & & ( ( steps ! = null & & steps . Count > 0 ) | | ( path ! = null & & path . Count > 0 ) ) ;
}
2022-08-15 18:26:40 +02:00
/// <summary>
/// Get the current goal
/// </summary>
/// <returns>Current goal of movement. Location.Zero if not set.</returns>
public Location GetCurrentMovementGoal ( )
{
2022-12-23 00:50:20 +08:00
return ( ! ClientIsMoving ( ) | | path = = null ) ? Location . Zero : path . Last ( ) ;
2022-08-15 18:26:40 +02:00
}
/// <summary>
/// Cancels the current movement
/// </summary>
/// <returns>True if there was an active path</returns>
public bool CancelMovement ( )
{
bool success = ClientIsMoving ( ) ;
path = null ;
return success ;
}
/// <summary>
/// Change the amount of sent movement packets per time
/// </summary>
/// <param name="newSpeed">Set a new walking type</param>
public void SetMovementSpeed ( MovementType newSpeed )
{
switch ( newSpeed )
{
case MovementType . Sneak :
// https://minecraft.fandom.com/wiki/Sneaking#Effects - Sneaking 1.31m/s
2022-10-05 15:02:30 +08:00
Config . Main . Advanced . MovementSpeed = 2 ;
2022-08-15 18:26:40 +02:00
break ;
case MovementType . Walk :
// https://minecraft.fandom.com/wiki/Walking#Usage - Walking 4.317 m/s
2022-10-05 15:02:30 +08:00
Config . Main . Advanced . MovementSpeed = 4 ;
2022-08-15 18:26:40 +02:00
break ;
case MovementType . Sprint :
// https://minecraft.fandom.com/wiki/Sprinting#Usage - Sprinting 5.612 m/s
2022-10-05 15:02:30 +08:00
Config . Main . Advanced . MovementSpeed = 5 ;
2022-08-15 18:26:40 +02:00
break ;
}
}
2020-06-20 15:18:34 +02:00
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="relative">If true, the location is relative to the current location</param>
2022-12-23 00:50:20 +08:00
public void UpdateLocation ( Location location )
2020-06-20 15:18:34 +02:00
{
2022-12-23 00:50:20 +08:00
this . location = location ;
locationReceived = true ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="yaw">Yaw to look at</param>
/// <param name="pitch">Pitch to look at</param>
public void UpdateLocation ( Location location , float yaw , float pitch )
{
2022-10-02 18:31:08 +08:00
_yaw = yaw ;
_pitch = pitch ;
2022-12-23 00:50:20 +08:00
UpdateLocation ( location ) ;
2020-06-20 15:18:34 +02:00
}
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="lookAtLocation">Block coordinates to look at</param>
public void UpdateLocation ( Location location , Location lookAtLocation )
{
double dx = lookAtLocation . X - ( location . X - 0.5 ) ;
double dy = lookAtLocation . Y - ( location . Y + 1 ) ;
double dz = lookAtLocation . Z - ( location . Z - 0.5 ) ;
double r = Math . Sqrt ( dx * dx + dy * dy + dz * dz ) ;
float yaw = Convert . ToSingle ( - Math . Atan2 ( dx , dz ) / Math . PI * 180 ) ;
float pitch = Convert . ToSingle ( - Math . Asin ( dy / r ) / Math . PI * 180 ) ;
if ( yaw < 0 ) yaw + = 360 ;
UpdateLocation ( location , yaw , pitch ) ;
}
/// <summary>
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
/// </summary>
/// <param name="location">The new location</param>
/// <param name="direction">Direction to look at</param>
public void UpdateLocation ( Location location , Direction direction )
{
float yaw = 0 ;
float pitch = 0 ;
2020-05-26 11:20:12 +02:00
switch ( direction )
{
case Direction . Up :
pitch = - 90 ;
break ;
case Direction . Down :
pitch = 90 ;
break ;
case Direction . East :
yaw = 270 ;
break ;
case Direction . West :
yaw = 90 ;
break ;
case Direction . North :
yaw = 180 ;
break ;
case Direction . South :
break ;
default :
2022-10-28 11:13:20 +08:00
throw new ArgumentException ( Translations . exception_unknown_direction , nameof ( direction ) ) ;
2020-05-26 11:20:12 +02:00
}
UpdateLocation ( location , yaw , pitch ) ;
}
/// <summary>
2022-08-15 23:55:44 +08:00
/// Received chat/system message from the server
2020-05-26 11:20:12 +02:00
/// </summary>
2022-08-15 23:55:44 +08:00
/// <param name="message">Message received</param>
2022-12-23 00:50:20 +08:00
public async Task OnTextReceivedAsync ( ChatMessage message )
2020-05-26 11:20:12 +02:00
{
2021-04-30 12:28:27 +08:00
UpdateKeepAlive ( ) ;
2020-06-20 15:39:16 +02:00
2022-08-15 23:55:44 +08:00
List < string > links = new ( ) ;
string messageText ;
2020-06-20 15:39:16 +02:00
2022-08-27 02:10:44 +08:00
if ( message . isSignedChat )
2022-08-15 23:55:44 +08:00
{
2022-10-05 15:02:30 +08:00
if ( ! Config . Signature . ShowIllegalSignedChat & & ! message . isSystemChat & & ! ( bool ) message . isSignatureLegal ! )
2022-08-27 02:10:44 +08:00
return ;
messageText = ChatParser . ParseSignedChat ( message , links ) ;
2022-08-15 23:55:44 +08:00
}
else
2020-05-26 11:20:12 +02:00
{
2022-08-27 02:10:44 +08:00
if ( message . isJson )
messageText = ChatParser . ParseText ( message . content , links ) ;
else
messageText = message . content ;
2020-05-26 11:20:12 +02:00
}
2020-06-20 15:39:16 +02:00
2022-08-15 23:55:44 +08:00
Log . Chat ( messageText ) ;
2020-06-20 15:39:16 +02:00
2022-10-05 15:02:30 +08:00
if ( Config . Main . Advanced . ShowChatLinks )
2020-05-26 11:20:12 +02:00
foreach ( string link in links )
2022-10-28 11:13:20 +08:00
Log . Chat ( string . Format ( Translations . mcc_link , link ) ) ;
2020-06-20 15:39:16 +02:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . TextReceive ,
new Tuple < string , string > ( messageText , message . content ) ) ;
2022-08-15 23:55:44 +08:00
DispatchBotEvent ( bot = > bot . GetText ( messageText ) ) ;
DispatchBotEvent ( bot = > bot . GetText ( messageText , message . content ) ) ;
2020-05-26 11:20:12 +02:00
}
2021-07-04 11:26:41 +05:00
2020-05-26 11:20:12 +02:00
/// <summary>
/// Received a connection keep-alive from the server
/// </summary>
public void OnServerKeepAlive ( )
{
2021-04-30 12:28:27 +08:00
UpdateKeepAlive ( ) ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// When an inventory is opened
/// </summary>
2020-07-07 13:21:55 +08:00
/// <param name="inventory">The inventory</param>
/// <param name="inventoryID">Inventory ID</param>
2022-12-23 00:50:20 +08:00
public async Task OnInventoryOpenAsync ( int inventoryID , Container inventory )
2020-05-26 11:20:12 +02:00
{
inventories [ inventoryID ] = inventory ;
if ( inventoryID ! = 0 )
{
2022-10-28 11:13:20 +08:00
Log . Info ( string . Format ( Translations . extra_inventory_open , inventoryID , inventory . Title ) ) ;
Log . Info ( Translations . extra_inventory_interact ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . InventoryOpen , inventoryID ) ;
2020-07-07 13:21:55 +08:00
DispatchBotEvent ( bot = > bot . OnInventoryOpen ( inventoryID ) ) ;
2020-05-26 11:20:12 +02:00
}
}
/// <summary>
/// When an inventory is close
/// </summary>
2020-07-07 13:21:55 +08:00
/// <param name="inventoryID">Inventory ID</param>
2022-12-23 00:50:20 +08:00
public async Task OnInventoryCloseAsync ( int inventoryID )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
lock ( inventoryLock )
2021-01-31 08:25:04 +08:00
{
2022-12-23 00:50:20 +08:00
if ( inventoryID = = 0 )
inventories [ 0 ] . Items . Clear ( ) ; // Don't delete player inventory
else
inventories . Remove ( inventoryID ) ;
2021-01-31 08:25:04 +08:00
}
2021-07-04 11:26:41 +05:00
2020-05-26 11:20:12 +02:00
if ( inventoryID ! = 0 )
2020-07-07 13:21:55 +08:00
{
2022-10-28 11:13:20 +08:00
Log . Info ( string . Format ( Translations . extra_inventory_close , inventoryID ) ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . InventoryClose , inventoryID ) ;
2020-07-07 13:21:55 +08:00
DispatchBotEvent ( bot = > bot . OnInventoryClose ( inventoryID ) ) ;
}
2020-05-26 11:20:12 +02:00
}
2022-10-12 19:51:01 +02:00
/// <summary>
/// When received window properties from server.
/// Used for Frunaces, Enchanting Table, Beacon, Brewing stand, Stone cutter, Loom and Lectern
/// More info about: https://wiki.vg/Protocol#Set_Container_Property
/// </summary>
/// <param name="inventoryID">Inventory ID</param>
/// <param name="propertyId">Property ID</param>
/// <param name="propertyValue">Property Value</param>
2022-12-23 00:50:20 +08:00
public async Task OnWindowPropertiesAsync ( byte inventoryID , short propertyId , short propertyValue )
2022-10-12 19:51:01 +02:00
{
2022-12-20 22:41:14 +08:00
if ( ! inventories . TryGetValue ( inventoryID , out Container ? inventory ) )
2022-10-12 19:51:01 +02:00
return ;
2022-12-20 22:41:14 +08:00
inventory . Properties . Remove ( propertyId ) ;
2022-10-12 19:51:01 +02:00
inventory . Properties . Add ( propertyId , propertyValue ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . InventoryProperties ,
new Tuple < int , int , int > ( inventoryID , propertyId , propertyValue ) ) ;
2022-10-12 19:51:01 +02:00
DispatchBotEvent ( bot = > bot . OnInventoryProperties ( inventoryID , propertyId , propertyValue ) ) ;
if ( inventory . Type = = ContainerType . Enchantment )
{
// We got the last property for enchantment
2022-10-14 15:33:33 +02:00
if ( propertyId = = 9 & & propertyValue ! = - 1 )
2022-10-12 19:51:01 +02:00
{
2022-10-14 15:33:33 +02:00
short topEnchantmentLevelRequirement = inventory . Properties [ 0 ] ;
short middleEnchantmentLevelRequirement = inventory . Properties [ 1 ] ;
short bottomEnchantmentLevelRequirement = inventory . Properties [ 2 ] ;
2022-10-12 19:51:01 +02:00
Enchantment topEnchantment = EnchantmentMapping . GetEnchantmentById (
GetProtocolVersion ( ) ,
2022-10-14 15:33:33 +02:00
inventory . Properties [ 4 ] ) ;
2022-10-12 19:51:01 +02:00
Enchantment middleEnchantment = EnchantmentMapping . GetEnchantmentById (
GetProtocolVersion ( ) ,
2022-10-14 15:33:33 +02:00
inventory . Properties [ 5 ] ) ;
2022-10-12 19:51:01 +02:00
Enchantment bottomEnchantment = EnchantmentMapping . GetEnchantmentById (
GetProtocolVersion ( ) ,
2022-10-14 15:33:33 +02:00
inventory . Properties [ 6 ] ) ;
short topEnchantmentLevel = inventory . Properties [ 7 ] ;
short middleEnchantmentLevel = inventory . Properties [ 8 ] ;
short bottomEnchantmentLevel = inventory . Properties [ 9 ] ;
StringBuilder sb = new ( ) ;
2022-10-28 11:13:20 +08:00
sb . AppendLine ( Translations . Enchantment_enchantments_available + ":" ) ;
2022-10-14 15:33:33 +02:00
2022-10-28 11:13:20 +08:00
sb . AppendLine ( Translations . Enchantment_tops_slot + ":\t"
2022-10-14 15:33:33 +02:00
+ EnchantmentMapping . GetEnchantmentName ( topEnchantment ) + " "
+ EnchantmentMapping . ConvertLevelToRomanNumbers ( topEnchantmentLevel ) + " ("
2022-10-28 11:13:20 +08:00
+ topEnchantmentLevelRequirement + " " + Translations . Enchantment_levels + ")" ) ;
2022-10-14 15:33:33 +02:00
2022-10-28 11:13:20 +08:00
sb . AppendLine ( Translations . Enchantment_middle_slot + ":\t"
2022-10-14 15:33:33 +02:00
+ EnchantmentMapping . GetEnchantmentName ( middleEnchantment ) + " "
+ EnchantmentMapping . ConvertLevelToRomanNumbers ( middleEnchantmentLevel ) + " ("
2022-10-28 11:13:20 +08:00
+ middleEnchantmentLevelRequirement + " " + Translations . Enchantment_levels + ")" ) ;
2022-10-14 15:33:33 +02:00
2022-10-28 11:13:20 +08:00
sb . AppendLine ( Translations . Enchantment_bottom_slot + ":\t"
2022-10-14 15:33:33 +02:00
+ EnchantmentMapping . GetEnchantmentName ( bottomEnchantment ) + " "
+ EnchantmentMapping . ConvertLevelToRomanNumbers ( bottomEnchantmentLevel ) + " ("
2022-10-28 11:13:20 +08:00
+ bottomEnchantmentLevelRequirement + " " + Translations . Enchantment_levels + ")" ) ;
2022-10-14 15:33:33 +02:00
Log . Info ( sb . ToString ( ) ) ;
2022-12-20 22:41:14 +08:00
lastEnchantment = new ( )
{
TopEnchantment = topEnchantment ,
MiddleEnchantment = middleEnchantment ,
BottomEnchantment = bottomEnchantment ,
2022-10-17 15:14:55 +02:00
2022-12-20 22:41:14 +08:00
Seed = inventory . Properties [ 3 ] ,
2022-10-17 15:14:55 +02:00
2022-12-20 22:41:14 +08:00
TopEnchantmentLevel = topEnchantmentLevel ,
MiddleEnchantmentLevel = middleEnchantmentLevel ,
BottomEnchantmentLevel = bottomEnchantmentLevel ,
2022-10-17 15:14:55 +02:00
2022-12-20 22:41:14 +08:00
TopEnchantmentLevelRequirement = topEnchantmentLevelRequirement ,
MiddleEnchantmentLevelRequirement = middleEnchantmentLevelRequirement ,
BottomEnchantmentLevelRequirement = bottomEnchantmentLevelRequirement
} ;
2022-10-17 15:14:55 +02:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . Enchantments , lastEnchantment ) ;
2022-10-14 15:33:33 +02:00
DispatchBotEvent ( bot = > bot . OnEnchantments (
// Enchantments
topEnchantment ,
middleEnchantment ,
bottomEnchantment ,
2022-10-12 19:51:01 +02:00
2022-10-14 15:33:33 +02:00
// Enchantment levels
topEnchantmentLevel ,
middleEnchantmentLevel ,
bottomEnchantmentLevel ,
2022-10-12 19:51:01 +02:00
2022-10-14 15:33:33 +02:00
// Required levels for enchanting
topEnchantmentLevelRequirement ,
middleEnchantmentLevelRequirement ,
bottomEnchantmentLevelRequirement ) ) ;
2022-10-17 15:14:55 +02:00
DispatchBotEvent ( bot = > bot . OnEnchantments ( lastEnchantment ) ) ;
2022-10-12 19:51:01 +02:00
}
}
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// When received window items from server.
/// </summary>
/// <param name="inventoryID">Inventory ID</param>
/// <param name="itemList">Item list, key = slot ID, value = Item information</param>
2022-12-23 00:50:20 +08:00
public async Task OnWindowItemsAsync ( byte inventoryID , Dictionary < int , Item > itemList , int stateId )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
if ( inventories . TryGetValue ( inventoryID , out Container ? container ) )
2020-07-07 11:26:49 +08:00
{
2022-12-20 22:41:14 +08:00
container . Items = itemList ;
container . StateID = stateId ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . InventoryUpdate , inventoryID ) ;
2020-07-07 11:26:49 +08:00
DispatchBotEvent ( bot = > bot . OnInventoryUpdate ( inventoryID ) ) ;
}
2020-05-26 11:20:12 +02:00
}
/// <summary>
/// When a slot is set inside window items
/// </summary>
/// <param name="inventoryID">Window ID</param>
/// <param name="slotID">Slot ID</param>
/// <param name="item">Item (may be null for empty slot)</param>
2022-12-23 00:50:20 +08:00
public async Task OnSetSlotAsync ( byte inventoryID , short slotID , Item ? item , int stateId )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
lock ( inventoryLock )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
if ( inventories . TryGetValue ( inventoryID , out Container ? container ) )
container . StateID = stateId ;
// Handle inventoryID -2 - Add item to player inventory without animation
if ( inventoryID = = 254 )
inventoryID = 0 ;
// Handle cursor item
if ( inventoryID = = 255 & & slotID = = - 1 )
2020-08-18 18:57:56 +08:00
{
2022-12-20 22:41:14 +08:00
inventoryID = 0 ; // Prevent key not found for some bots relied to this event
if ( inventories . ContainsKey ( 0 ) )
{
if ( item ! = null )
inventories [ 0 ] . Items [ - 1 ] = item ;
else
inventories [ 0 ] . Items . Remove ( - 1 ) ;
}
2020-08-18 18:57:56 +08:00
}
2022-12-20 22:41:14 +08:00
else
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
if ( inventories . ContainsKey ( inventoryID ) )
2020-07-05 15:48:19 +08:00
{
2022-12-20 22:41:14 +08:00
if ( item = = null | | item . IsEmpty )
2020-07-05 15:48:19 +08:00
inventories [ inventoryID ] . Items . Remove ( slotID ) ;
2022-12-20 22:41:14 +08:00
else
inventories [ inventoryID ] . Items [ slotID ] = item ;
2020-07-05 15:48:19 +08:00
}
2020-05-26 11:20:12 +02:00
}
}
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . InventoryUpdate , inventoryID ) ;
2020-07-07 11:26:49 +08:00
DispatchBotEvent ( bot = > bot . OnInventoryUpdate ( inventoryID ) ) ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// Set client player's ID for later receiving player's own properties
2020-05-26 11:20:12 +02:00
/// </summary>
2020-06-20 15:18:34 +02:00
/// <param name="EntityID">Player Entity ID</param>
public void OnReceivePlayerEntityID ( int EntityID )
2020-05-26 11:20:12 +02:00
{
2020-06-20 15:18:34 +02:00
playerEntityID = EntityID ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// Triggered when a new player joins the game
2020-05-26 11:20:12 +02:00
/// </summary>
2022-08-15 23:55:44 +08:00
/// <param name="player">player info</param>
2022-12-23 00:50:20 +08:00
public async Task OnPlayerJoinAsync ( PlayerInfo player )
2020-05-26 11:20:12 +02:00
{
2020-06-20 15:18:34 +02:00
//Ignore placeholders eg 0000tab# from TabListPlus
2022-08-15 23:55:44 +08:00
if ( ! ChatBot . IsValidName ( player . Name ) )
2020-06-20 15:18:34 +02:00
return ;
2022-08-15 23:55:44 +08:00
if ( player . Name = = username )
{
// 1.19+ offline server is possible to return different uuid
2022-10-02 18:31:08 +08:00
uuid = player . Uuid ;
uuidStr = player . Uuid . ToString ( ) . Replace ( "-" , string . Empty ) ;
2022-08-15 23:55:44 +08:00
}
2020-06-20 15:18:34 +02:00
lock ( onlinePlayers )
2020-05-26 11:20:12 +02:00
{
2022-08-27 02:10:44 +08:00
onlinePlayers [ player . Uuid ] = player ;
2020-05-26 11:20:12 +02:00
}
2020-08-06 21:57:23 +08:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerJoin , player ) ;
2022-08-27 02:10:44 +08:00
DispatchBotEvent ( bot = > bot . OnPlayerJoin ( player . Uuid , player . Name ) ) ;
2020-05-26 11:20:12 +02:00
}
/// <summary>
2020-06-20 15:18:34 +02:00
/// Triggered when a player has left the game
2020-05-26 11:20:12 +02:00
/// </summary>
2020-06-20 15:18:34 +02:00
/// <param name="uuid">UUID of the player</param>
2022-12-23 00:50:20 +08:00
public async Task OnPlayerLeaveAsync ( Guid uuid )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
PlayerInfo ? playerInfo = null ;
2020-08-06 16:01:14 +02:00
2020-06-20 15:18:34 +02:00
lock ( onlinePlayers )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
if ( onlinePlayers . TryGetValue ( uuid , out playerInfo ) )
2020-08-07 11:58:44 +02:00
onlinePlayers . Remove ( uuid ) ;
2020-05-26 11:20:12 +02:00
}
2020-08-06 21:57:23 +08:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerLeave ,
new Tuple < Guid , PlayerInfo ? > ( uuid , playerInfo ) ) ;
2020-08-06 16:01:14 +02:00
DispatchBotEvent ( bot = > bot . OnPlayerLeave ( uuid , username ) ) ;
2020-05-26 11:20:12 +02:00
}
2022-10-26 20:53:09 +02:00
// <summary>
/// This method is called when a player has been killed by another entity
/// </summary>
/// <param name="playerEntity">Victim's entity</param>
/// <param name="killerEntity">Killer's entity</param>
2022-12-23 00:50:20 +08:00
public async Task OnPlayerKilledAsync ( int killerEntityId , string chatMessage )
2022-10-26 20:53:09 +02:00
{
2022-12-23 00:50:20 +08:00
if ( ! entities . TryGetValue ( killerEntityId , out Entity ? killer ) )
2022-10-26 20:53:09 +02:00
return ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerKilled ,
new Tuple < Entity , string > ( killer , chatMessage ) ) ;
DispatchBotEvent ( bot = > bot . OnKilled ( killer , chatMessage ) ) ;
2022-10-26 20:53:09 +02:00
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when a plugin channel message was sent from the server.
/// </summary>
/// <param name="channel">The channel the message was sent on</param>
/// <param name="data">The data from the channel</param>
public void OnPluginChannelMessage ( string channel , byte [ ] data )
{
if ( channel = = "REGISTER" )
{
string [ ] channels = Encoding . UTF8 . GetString ( data ) . Split ( '\0' ) ;
foreach ( string chan in channels )
{
if ( ! registeredServerPluginChannels . Contains ( chan ) )
{
registeredServerPluginChannels . Add ( chan ) ;
}
}
}
if ( channel = = "UNREGISTER" )
{
string [ ] channels = Encoding . UTF8 . GetString ( data ) . Split ( '\0' ) ;
foreach ( string chan in channels )
{
registeredServerPluginChannels . Remove ( chan ) ;
}
}
2022-12-20 22:41:14 +08:00
if ( registeredBotPluginChannels . TryGetValue ( channel , out List < ChatBot > ? channelList ) )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
DispatchBotEvent ( bot = > bot . OnPluginMessage ( channel , data ) , channelList ) ;
2020-05-26 11:20:12 +02:00
}
}
2020-06-20 15:18:34 +02:00
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when an entity spawned
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnSpawnEntity ( Entity entity )
2020-05-26 11:20:12 +02:00
{
// The entity should not already exist, but if it does, let's consider the previous one is being destroyed
if ( entities . ContainsKey ( entity . ID ) )
2022-12-23 00:50:20 +08:00
await OnDestroyEntities ( new [ ] { entity . ID } ) ;
2020-05-26 11:20:12 +02:00
entities . Add ( entity . ID , entity ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . EntitySpawn , entity ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnEntitySpawn ( entity ) ) ;
2020-05-26 11:20:12 +02:00
}
2021-07-04 11:26:41 +05:00
2020-07-04 13:45:51 +05:00
/// <summary>
/// Called when an entity effects
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnEntityEffect ( int entityid , Effect effect )
2020-07-04 13:45:51 +05:00
{
2022-12-23 00:50:20 +08:00
if ( ! entities . TryGetValue ( entityid , out Entity ? entity ) )
return ;
await TriggerEvent ( McClientEventType . EntityEffect ,
new Tuple < Entity , Effect > ( entity , effect ) ) ;
2020-05-26 11:20:12 +02:00
2022-12-23 00:50:20 +08:00
byte flag = ( byte ) ( ( effect . IsFromBeacon ? 1 : 0 ) | ( effect . ShowParticles ? 2 : 0 ) | ( effect . ShowIcon ? 2 : 0 ) ) ;
DispatchBotEvent ( bot = > bot . OnEntityEffect ( entity , effect . Type , effect . EffectLevel - 1 , effect . DurationInTick , flag ) ) ;
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when a player spawns or enters the client's render distance
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnSpawnPlayer ( int entityID , Guid uuid , Location location , byte yaw , byte pitch )
2020-05-26 11:20:12 +02:00
{
2022-09-09 16:13:25 +08:00
Entity playerEntity ;
if ( onlinePlayers . TryGetValue ( uuid , out PlayerInfo ? player ) )
{
playerEntity = new ( entityID , EntityType . Player , location , uuid , player . Name , yaw , pitch ) ;
player . entity = playerEntity ;
}
else
playerEntity = new ( entityID , EntityType . Player , location , uuid , null , yaw , pitch ) ;
2022-12-23 00:50:20 +08:00
await OnSpawnEntity ( playerEntity ) ;
2020-05-26 11:20:12 +02:00
}
2020-06-20 15:18:34 +02:00
2020-06-20 17:57:07 +05:00
/// <summary>
/// Called on Entity Equipment
/// </summary>
/// <param name="entityid"> Entity ID</param>
2022-11-28 13:55:05 +08:00
/// <param name="slot"> Equipment slot. 0: main hand, 1: off hand, 2-5: armor slot (2: boots, 3: leggings, 4: chestplate, 5: helmet)</param>
2020-06-20 17:57:07 +05:00
/// <param name="item"> Item)</param>
2022-12-23 00:50:20 +08:00
public async Task OnEntityEquipment ( int entityid , int slot , Item ? item )
2020-06-20 17:57:07 +05:00
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( entityid , out Entity ? entity ) )
2020-06-20 17:57:07 +05:00
{
2022-12-20 22:41:14 +08:00
entity . Equipment . Remove ( slot ) ;
2020-08-26 21:58:45 +05:00
if ( item ! = null )
entity . Equipment [ slot ] = item ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . EntityEquipment ,
new Tuple < Entity , int , Item ? > ( entity , slot , item ) ) ;
DispatchBotEvent ( bot = > bot . OnEntityEquipment ( entity , slot , item ) ) ;
2020-06-20 17:57:07 +05:00
}
}
2020-06-20 15:18:34 +02:00
2020-05-29 23:18:34 +05:00
/// <summary>
/// Called when the Game Mode has been updated for a player
/// </summary>
/// <param name="playername">Player Name</param>
2020-06-13 18:00:30 +05:00
/// <param name="uuid">Player UUID (Empty for initial gamemode on login)</param>
2020-05-29 23:18:34 +05:00
/// <param name="gamemode">New Game Mode (0: Survival, 1: Creative, 2: Adventure, 3: Spectator).</param>
2022-12-20 22:41:14 +08:00
public async Task OnGamemodeUpdate ( Guid uuid , int gamemode )
2020-05-29 23:18:34 +05:00
{
2020-06-13 18:00:30 +05:00
// Initial gamemode on login
if ( uuid = = Guid . Empty )
this . gamemode = gamemode ;
// Further regular gamemode change events
2022-12-20 22:41:14 +08:00
if ( onlinePlayers . TryGetValue ( uuid , out PlayerInfo ? playerInfo ) )
2020-05-29 23:18:34 +05:00
{
2022-12-20 22:41:14 +08:00
if ( playerInfo . Name = = username )
2020-06-13 18:00:30 +05:00
this . gamemode = gamemode ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . GamemodeUpdate ,
new Tuple < PlayerInfo , int > ( playerInfo , gamemode ) ) ;
2022-12-20 22:41:14 +08:00
DispatchBotEvent ( bot = > bot . OnGamemodeUpdate ( playerInfo . Name , uuid , gamemode ) ) ;
2020-05-29 23:18:34 +05:00
}
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when entities dead/despawn.
/// </summary>
2022-12-23 00:50:20 +08:00
public async Task OnDestroyEntities ( int [ ] Entities )
2020-05-26 11:20:12 +02:00
{
foreach ( int a in Entities )
{
2022-10-02 13:49:36 +08:00
if ( entities . TryGetValue ( a , out Entity ? entity ) )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . EntityDespawn , entity ) ;
2022-10-02 13:49:36 +08:00
DispatchBotEvent ( bot = > bot . OnEntityDespawn ( entity ) ) ;
2020-05-26 11:20:12 +02:00
entities . Remove ( a ) ;
}
}
}
/// <summary>
/// Called when an entity's position changed within 8 block of its previous position.
/// </summary>
/// <param name="EntityID"></param>
/// <param name="Dx"></param>
/// <param name="Dy"></param>
/// <param name="Dz"></param>
/// <param name="onGround"></param>
2022-12-23 00:50:20 +08:00
public async Task OnEntityPosition ( int EntityID , Double Dx , Double Dy , Double Dz , bool onGround )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( EntityID , out Entity ? entity ) )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
entity . Location . X + = Dx ;
entity . Location . Y + = Dy ;
entity . Location . Z + = Dz ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . EntityMove , entity ) ;
2022-12-20 22:41:14 +08:00
DispatchBotEvent ( bot = > bot . OnEntityMove ( entity ) ) ;
2020-05-26 11:20:12 +02:00
}
}
2020-06-20 15:18:34 +02:00
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when an entity moved over 8 block.
/// </summary>
/// <param name="EntityID"></param>
/// <param name="X"></param>
/// <param name="Y"></param>
/// <param name="Z"></param>
/// <param name="onGround"></param>
2022-12-23 00:50:20 +08:00
public async Task OnEntityTeleport ( int EntityID , Double X , Double Y , Double Z , bool onGround )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( EntityID , out Entity ? entity ) )
2020-05-26 11:20:12 +02:00
{
2022-12-20 22:41:14 +08:00
entity . Location = new Location ( X , Y , Z ) ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . EntityMove , entity ) ;
2022-12-20 22:41:14 +08:00
DispatchBotEvent ( bot = > bot . OnEntityMove ( entity ) ) ;
2020-05-26 11:20:12 +02:00
}
}
/// <summary>
/// Called when received entity properties from server.
/// </summary>
/// <param name="EntityID"></param>
/// <param name="prop"></param>
2022-12-23 00:50:20 +08:00
public async Task OnEntityProperties ( int EntityID , Dictionary < string , double > prop )
2020-05-26 11:20:12 +02:00
{
2020-06-20 15:18:34 +02:00
if ( EntityID = = playerEntityID )
2020-05-26 11:20:12 +02:00
{
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerPropertyReceive , prop ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnPlayerProperty ( prop ) ) ;
2020-05-26 11:20:12 +02:00
}
}
2020-06-20 15:18:34 +02:00
2021-03-21 22:17:19 +08:00
/// <summary>
/// Called when the status of an entity have been changed
/// </summary>
/// <param name="entityID">Entity ID</param>
/// <param name="status">Status ID</param>
2022-12-23 00:50:20 +08:00
public async Task OnEntityStatus ( int entityID , byte status )
2021-03-21 22:17:19 +08:00
{
if ( entityID = = playerEntityID )
{
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerStatusUpdate , status ) ;
2021-03-21 22:17:19 +08:00
DispatchBotEvent ( bot = > bot . OnPlayerStatus ( status ) ) ;
}
}
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when server sent a Time Update packet.
/// </summary>
/// <param name="WorldAge"></param>
/// <param name="TimeOfDay"></param>
2022-12-23 00:50:20 +08:00
public async Task OnTimeUpdate ( long WorldAge , long TimeOfDay )
2020-05-26 11:20:12 +02:00
{
2021-04-30 12:28:27 +08:00
// TimeUpdate sent every server tick hence used as timeout detect
UpdateKeepAlive ( ) ;
2020-05-26 11:20:12 +02:00
// calculate server tps
if ( lastAge ! = 0 )
{
DateTime currentTime = DateTime . Now ;
long tickDiff = WorldAge - lastAge ;
2022-12-23 00:50:20 +08:00
double tps = tickDiff / ( currentTime - lastTime ) . TotalSeconds ;
2020-05-26 11:20:12 +02:00
lastAge = WorldAge ;
lastTime = currentTime ;
2020-08-17 17:40:06 +08:00
if ( tps < = 20 & & tps > 0 )
2020-05-26 11:20:12 +02:00
{
2020-08-17 17:40:06 +08:00
// calculate average tps
if ( tpsSamples . Count > = maxSamples )
{
// full
sampleSum - = tpsSamples [ 0 ] ;
tpsSamples . RemoveAt ( 0 ) ;
}
tpsSamples . Add ( tps ) ;
sampleSum + = tps ;
averageTPS = sampleSum / tpsSamples . Count ;
2020-05-26 11:20:12 +02:00
serverTPS = tps ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . ServerTpsUpdate , tps ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnServerTpsUpdate ( tps ) ) ;
2020-05-26 11:20:12 +02:00
}
}
else
{
lastAge = WorldAge ;
lastTime = DateTime . Now ;
}
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . TimeUpdate ,
new Tuple < long , long > ( WorldAge , TimeOfDay ) ) ;
2020-08-07 13:35:23 +05:00
DispatchBotEvent ( bot = > bot . OnTimeUpdate ( WorldAge , TimeOfDay ) ) ;
2020-05-26 11:20:12 +02:00
}
2020-05-29 23:18:34 +05:00
2020-05-26 11:20:12 +02:00
/// <summary>
/// Called when client player's health changed, e.g. getting attack
/// </summary>
/// <param name="health">Player current health</param>
2022-12-23 00:50:20 +08:00
public async Task OnUpdateHealth ( float health , int food )
2020-05-26 11:20:12 +02:00
{
playerHealth = health ;
playerFoodSaturation = food ;
2020-06-20 15:39:16 +02:00
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . HealthUpdate ,
new Tuple < float , int > ( health , food ) ) ;
DispatchBotEvent ( bot = > bot . OnHealthUpdate ( health , food ) ) ;
2020-05-26 11:20:12 +02:00
if ( health < = 0 )
{
2022-10-05 15:02:30 +08:00
if ( Config . Main . Advanced . AutoRespawn )
2020-05-26 11:20:12 +02:00
{
2022-10-28 11:13:20 +08:00
Log . Info ( Translations . mcc_player_dead_respawn ) ;
2022-12-20 22:41:14 +08:00
respawnTicks = 20 ;
2020-05-26 11:20:12 +02:00
}
else
{
2022-10-28 11:13:20 +08:00
Log . Info ( string . Format ( Translations . mcc_player_dead , Config . Main . Advanced . InternalCmdChar . ToLogString ( ) ) ) ;
2020-05-26 11:20:12 +02:00
}
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . Death , null ) ;
2020-08-19 00:08:19 +05:00
DispatchBotEvent ( bot = > bot . OnDeath ( ) ) ;
2020-05-26 11:20:12 +02:00
}
}
2020-05-29 23:18:34 +05:00
/// <summary>
/// Called when experience updates
/// </summary>
/// <param name="Experiencebar">Between 0 and 1</param>
/// <param name="Level">Level</param>
/// <param name="TotalExperience">Total Experience</param>
2022-12-23 00:50:20 +08:00
public async Task OnSetExperience ( float Experiencebar , int Level , int TotalExperience )
2020-05-29 23:18:34 +05:00
{
playerLevel = Level ;
playerTotalExperience = TotalExperience ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . ExperienceChange ,
new Tuple < float , int , int > ( Experiencebar , Level , TotalExperience ) ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnSetExperience ( Experiencebar , Level , TotalExperience ) ) ;
2020-05-29 23:18:34 +05:00
}
/// <summary>
/// Called when and explosion occurs on the server
/// </summary>
/// <param name="location">Explosion location</param>
/// <param name="strength">Explosion strength</param>
/// <param name="affectedBlocks">Amount of affected blocks</param>
2022-12-23 00:50:20 +08:00
public async Task OnExplosion ( Location location , float strength , int affectedBlocks )
2020-05-29 23:18:34 +05:00
{
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . Explosion ,
new Tuple < Location , float , int > ( location , strength , affectedBlocks ) ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnExplosion ( location , strength , affectedBlocks ) ) ;
2020-05-29 23:18:34 +05:00
}
2020-06-20 15:18:34 +02:00
2020-06-07 16:16:49 +05:00
/// <summary>
/// Called when Latency is updated
/// </summary>
/// <param name="uuid">player uuid</param>
/// <param name="latency">Latency</param>
2022-12-23 00:50:20 +08:00
public async Task OnLatencyUpdate ( Guid uuid , int latency )
2020-06-07 16:16:49 +05:00
{
2022-12-21 14:01:05 +08:00
if ( onlinePlayers . TryGetValue ( uuid , out PlayerInfo ? player ) )
2020-06-07 16:16:49 +05:00
{
2022-08-15 23:55:44 +08:00
player . Ping = latency ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . PlayerLatencyUpdate ,
new Tuple < PlayerInfo , int > ( player , latency ) ) ;
2022-08-15 23:55:44 +08:00
string playerName = player . Name ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnLatencyUpdate ( playerName , uuid , latency ) ) ;
2022-12-23 00:50:20 +08:00
if ( player . entity ! = null )
DispatchBotEvent ( bot = > bot . OnLatencyUpdate ( player . entity , playerName , uuid , latency ) ) ;
2020-06-07 16:16:49 +05:00
}
}
2020-06-20 15:18:34 +02:00
2020-05-29 23:18:34 +05:00
/// <summary>
2020-06-20 17:57:07 +05:00
/// Called when held item change
2020-05-29 23:18:34 +05:00
/// </summary>
2020-06-20 17:57:07 +05:00
/// <param name="slot"> item slot</param>
2022-12-23 00:50:20 +08:00
public async Task OnHeldItemChange ( byte slot )
2020-05-26 11:20:12 +02:00
{
CurrentSlot = slot ;
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . HeldItemChange , slot ) ;
2020-06-20 15:39:16 +02:00
DispatchBotEvent ( bot = > bot . OnHeldItemChange ( slot ) ) ;
2020-05-26 11:20:12 +02:00
}
2020-06-20 15:18:34 +02:00
2020-06-20 17:57:07 +05:00
/// <summary>
2022-09-18 00:18:27 +02:00
/// Called when an update of the map is sent by the server, take a look at https://wiki.vg/Protocol#Map_Data for more info on the fields
/// Map format and colors: https://minecraft.fandom.com/wiki/Map_item_format
/// </summary>
/// <param name="mapid">Map ID of the map being modified</param>
/// <param name="scale">A scale of the Map, from 0 for a fully zoomed-in map (1 block per pixel) to 4 for a fully zoomed-out map (16 blocks per pixel)</param>
/// <param name="trackingposition">Specifies whether player and item frame icons are shown </param>
/// <param name="locked">True if the map has been locked in a cartography table </param>
/// <param name="icons">A list of MapIcon objects of map icons, send only if trackingPosition is true</param>
/// <param name="columnsUpdated">Numbs of columns that were updated (map width) (NOTE: If it is 0, the next fields are not used/are set to default values of 0 and null respectively)</param>
/// <param name="rowsUpdated">Map height</param>
/// <param name="mapCoulmnX">x offset of the westernmost column</param>
/// <param name="mapRowZ">z offset of the northernmost row</param>
/// <param name="colors">a byte array of colors on the map</param>
2022-12-23 00:50:20 +08:00
public async Task OnMapData ( MapData mapData )
2022-09-18 00:18:27 +02:00
{
2022-12-23 00:50:20 +08:00
await TriggerEvent ( McClientEventType . MapDataReceive , mapData ) ;
DispatchBotEvent ( bot = > bot . OnMapData ( mapData . MapId ,
mapData . Scale ,
mapData . TrackingPosition ,
mapData . Locked ,
mapData . Icons ,
mapData . ColumnsUpdated ,
mapData . RowsUpdated ,
mapData . MapCoulmnX ,
mapData . MapRowZ ,
mapData . Colors ) ) ;
2020-06-20 17:57:07 +05:00
}
2020-06-20 15:18:34 +02:00
2020-06-20 17:57:07 +05:00
/// <summary>
/// Received some Title from the server
/// <param name="action"> 0 = set title, 1 = set subtitle, 3 = set action bar, 4 = set times and display, 4 = hide, 5 = reset</param>
/// <param name="titletext"> title text</param>
/// <param name="subtitletext"> suntitle text</param>
/// <param name="actionbartext"> action bar text</param>
/// <param name="fadein"> Fade In</param>
/// <param name="stay"> Stay</param>
/// <param name="fadeout"> Fade Out</param>
/// <param name="json"> json text</param>
2022-12-23 00:50:20 +08:00
public async Task OnTitle ( TitlePacket title )
{
await TriggerEvent ( McClientEventType . TitleReceive , title ) ;
DispatchBotEvent ( bot = > bot . OnTitle ( title . Action ,
title . TitleText ,
title . SubtitleText ,
title . ActionbarText ,
title . FadeIn ,
title . Stay ,
title . FadeOut ,
title . JsonText ) ) ;
2020-06-20 17:57:07 +05:00
}
2021-07-04 11:26:41 +05:00
2020-07-04 13:45:51 +05:00
/// <summary>
/// Called when coreboardObjective
/// </summary>
/// <param name="objectivename">objective name</param>
/// <param name="mode">0 to create the scoreboard. 1 to remove the scoreboard. 2 to update the display text.</param>
/// <param name="objectivevalue">Only if mode is 0 or 2. The text to be displayed for the score</param>
/// <param name="type">Only if mode is 0 or 2. 0 = "integer", 1 = "hearts".</param>
public void OnScoreboardObjective ( string objectivename , byte mode , string objectivevalue , int type )
{
string json = objectivevalue ;
objectivevalue = ChatParser . ParseText ( objectivevalue ) ;
DispatchBotEvent ( bot = > bot . OnScoreboardObjective ( objectivename , mode , objectivevalue , type , json ) ) ;
}
2021-07-04 11:26:41 +05:00
2020-07-04 13:45:51 +05:00
/// <summary>
/// Called when DisplayScoreboard
/// </summary>
/// <param name="entityname">The entity whose score this is. For players, this is their username; for other entities, it is their UUID.</param>
/// <param name="action">0 to create/update an item. 1 to remove an item.</param>
/// <param name="objectivename">The name of the objective the score belongs to</param>
/// <param name="value">he score to be displayed next to the entry. Only sent when Action does not equal 1.</param>
2022-07-24 22:21:15 +08:00
public void OnUpdateScore ( string entityname , int action , string objectivename , int value )
2020-07-04 13:45:51 +05:00
{
DispatchBotEvent ( bot = > bot . OnUpdateScore ( entityname , action , objectivename , value ) ) ;
}
2020-08-15 21:32:46 +08:00
/// <summary>
/// Called when the health of an entity changed
/// </summary>
/// <param name="entityID">Entity ID</param>
/// <param name="health">The health of the entity</param>
public void OnEntityHealth ( int entityID , float health )
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( entityID , out Entity ? entity ) )
2020-08-15 21:32:46 +08:00
{
2022-12-20 22:41:14 +08:00
entity . Health = health ;
DispatchBotEvent ( bot = > bot . OnEntityHealth ( entity , health ) ) ;
2020-08-20 21:36:50 +05:00
}
}
/// <summary>
/// Called when the metadata of an entity changed
/// </summary>
/// <param name="entityID">Entity ID</param>
/// <param name="metadata">The metadata of the entity</param>
2022-09-15 21:11:47 +08:00
public void OnEntityMetadata ( int entityID , Dictionary < int , object? > metadata )
2020-08-20 21:36:50 +05:00
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( entityID , out Entity ? entity ) )
2020-08-20 21:36:50 +05:00
{
2020-08-22 14:17:31 +05:00
entity . Metadata = metadata ;
2022-09-15 21:11:47 +08:00
if ( entity . Type . ContainsItem ( ) & & metadata . TryGetValue ( 7 , out object? itemObj ) & & itemObj ! = null & & itemObj . GetType ( ) = = typeof ( Item ) )
2020-08-20 21:36:50 +05:00
{
2022-09-15 21:11:47 +08:00
Item item = ( Item ) itemObj ;
2020-08-27 22:33:45 +05:00
if ( item = = null )
2020-08-22 14:17:31 +05:00
entity . Item = new Item ( ItemType . Air , 0 , null ) ;
2020-08-27 22:33:45 +05:00
else entity . Item = item ;
2020-08-22 14:17:31 +05:00
}
2022-09-15 21:11:47 +08:00
if ( metadata . TryGetValue ( 6 , out object? poseObj ) & & poseObj ! = null & & poseObj . GetType ( ) = = typeof ( Int32 ) )
2020-08-22 14:17:31 +05:00
{
2022-09-15 21:11:47 +08:00
entity . Pose = ( EntityPose ) poseObj ;
2020-08-22 14:17:31 +05:00
}
2022-09-15 21:11:47 +08:00
if ( metadata . TryGetValue ( 2 , out object? nameObj ) & & nameObj ! = null & & nameObj . GetType ( ) = = typeof ( string ) )
2020-08-22 14:17:31 +05:00
{
2022-10-02 18:31:08 +08:00
string name = nameObj . ToString ( ) ? ? string . Empty ;
2022-09-15 21:11:47 +08:00
entity . CustomNameJson = name ;
entity . CustomName = ChatParser . ParseText ( name ) ;
2020-08-22 14:17:31 +05:00
}
2022-09-15 21:11:47 +08:00
if ( metadata . TryGetValue ( 3 , out object? nameVisableObj ) & & nameVisableObj ! = null & & nameVisableObj . GetType ( ) = = typeof ( bool ) )
2020-08-22 14:17:31 +05:00
{
2022-10-02 18:31:08 +08:00
entity . IsCustomNameVisible = bool . Parse ( nameVisableObj . ToString ( ) ? ? string . Empty ) ;
2020-08-22 14:17:31 +05:00
}
DispatchBotEvent ( bot = > bot . OnEntityMetadata ( entity , metadata ) ) ;
2020-08-15 21:32:46 +08:00
}
}
2020-11-08 23:39:07 +01:00
/// <summary>
/// Called when tradeList is recieved after interacting with villager
/// </summary>
/// <param name="windowID">Window ID</param>
/// <param name="trades">List of trades.</param>
/// <param name="villagerInfo">Contains Level, Experience, IsRegularVillager and CanRestock .</param>
public void OnTradeList ( int windowID , List < VillagerTrade > trades , VillagerInfo villagerInfo )
{
DispatchBotEvent ( bot = > bot . OnTradeList ( windowID , trades , villagerInfo ) ) ;
}
2021-07-04 11:26:41 +05:00
/// <summary>
/// Will be called every player break block in gamemode 0
/// </summary>
/// <param name="entityId">Player ID</param>
/// <param name="location">Block location</param>
/// <param name="stage">Destroy stage, maximum 255</param>
public void OnBlockBreakAnimation ( int entityId , Location location , byte stage )
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( entityId , out Entity ? entity ) )
2021-07-04 11:26:41 +05:00
{
DispatchBotEvent ( bot = > bot . OnBlockBreakAnimation ( entity , location , stage ) ) ;
}
}
/// <summary>
/// Will be called every animations of the hit and place block
/// </summary>
/// <param name="entityID">Player ID</param>
/// <param name="animation">0 = LMB, 1 = RMB (RMB Corrent not work)</param>
public void OnEntityAnimation ( int entityID , byte animation )
{
2022-12-20 22:41:14 +08:00
if ( entities . TryGetValue ( entityID , out Entity ? entity ) )
2021-07-04 11:26:41 +05:00
{
DispatchBotEvent ( bot = > bot . OnEntityAnimation ( entity , animation ) ) ;
}
}
2022-08-15 23:55:44 +08:00
/// <summary>
/// Will be called when a Synchronization sequence is recevied, this sequence need to be sent when breaking or placing blocks
/// </summary>
/// <param name="sequenceId">Sequence ID</param>
public void OnBlockChangeAck ( int sequenceId )
{
this . sequenceId = sequenceId ;
}
/// <summary>
/// This method is called when the protocol handler receives server data
/// </summary>
/// <param name="hasMotd">Indicates if the server has a motd message</param>
/// <param name="motd">Server MOTD message</param>
/// <param name="hasIcon">Indicates if the server has a an icon</param>
/// <param name="iconBase64">Server icon in Base 64 format</param>
/// <param name="previewsChat">Indicates if the server previews chat</param>
public void OnServerDataRecived ( bool hasMotd , string motd , bool hasIcon , string iconBase64 , bool previewsChat )
{
2022-10-02 18:31:08 +08:00
isSupportPreviewsChat = previewsChat ;
2022-08-15 23:55:44 +08:00
}
/// <summary>
/// This method is called when the protocol handler receives "Set Display Chat Preview" packet
/// </summary>
/// <param name="previewsChat">Indicates if the server previews chat</param>
public void OnChatPreviewSettingUpdate ( bool previewsChat )
{
2022-10-02 18:31:08 +08:00
isSupportPreviewsChat = previewsChat ;
2022-08-15 23:55:44 +08:00
}
/// <summary>
/// This method is called when the protocol handler receives "Login Success" packet
/// </summary>
/// <param name="uuid">The player's UUID received from the server</param>
/// <param name="userName">The player's username received from the server</param>
/// <param name="playerProperty">Tuple<Name, Value, Signature(empty if there is no signature)></param>
public void OnLoginSuccess ( Guid uuid , string userName , Tuple < string , string , string > [ ] ? playerProperty )
{
//string UUID = uuid.ToString().Replace("-", String.Empty);
//Log.Info("now UUID = " + this.uuid);
//Log.Info("new UUID = " + UUID);
////handler.SetUserUUID(UUID);
}
2022-10-03 11:39:25 +08:00
/// <summary>
/// Used for a wide variety of game events, from weather to bed use to gamemode to demo messages.
/// </summary>
/// <param name="reason">Event type</param>
/// <param name="value">Depends on Reason</param>
public void OnGameEvent ( byte reason , float value )
{
switch ( reason )
{
case 7 :
DispatchBotEvent ( bot = > bot . OnRainLevelChange ( value ) ) ;
break ;
case 8 :
DispatchBotEvent ( bot = > bot . OnThunderLevelChange ( value ) ) ;
break ;
}
}
2022-10-08 17:56:32 +08:00
/// <summary>
/// Called when a block is changed.
/// </summary>
/// <param name="location">The location of the block.</param>
/// <param name="block">The block</param>
public void OnBlockChange ( Location location , Block block )
{
world . SetBlock ( location , block ) ;
DispatchBotEvent ( bot = > bot . OnBlockChange ( location , block ) ) ;
}
2022-12-06 15:50:17 +08:00
/// <summary>
/// Called when "AutoComplete" completes.
/// </summary>
/// <param name="transactionId">The number of this result.</param>
/// <param name="result">All commands.</param>
public void OnAutoCompleteDone ( int transactionId , string [ ] result )
{
ConsoleIO . OnAutoCompleteDone ( transactionId , result ) ;
}
2022-10-12 19:51:01 +02:00
/// <summary>
/// Send a click container button packet to the server.
/// Used for Enchanting table, Lectern, stone cutter and loom
/// </summary>
/// <param name="windowId">Id of the window being clicked</param>
/// <param name="buttonId">Id of the clicked button</param>
/// <returns>True if packet was successfully sent</returns>
2022-12-20 22:41:14 +08:00
public async Task < bool > ClickContainerButton ( int windowId , int buttonId )
2022-10-12 19:51:01 +02:00
{
2022-12-20 22:41:14 +08:00
return await handler ! . ClickContainerButton ( windowId , buttonId ) ;
2022-10-12 19:51:01 +02:00
}
2020-06-20 15:18:34 +02:00
#endregion
2020-05-26 11:20:12 +02:00
}
}