2013-07-18 09:27:19 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Net.Sockets ;
using System.Threading ;
using System.IO ;
using System.Net ;
2014-05-31 01:59:03 +02:00
using MinecraftClient.Protocol ;
using MinecraftClient.Proxy ;
2015-10-23 16:54:36 -07:00
using MinecraftClient.Protocol.Handlers.Forge ;
2015-11-27 17:16:33 +01:00
using MinecraftClient.Mapping ;
2020-03-26 15:01:42 +08:00
using MinecraftClient.Inventory ;
2020-04-01 18:28:00 +08:00
using System.Threading.Tasks ;
2013-07-18 09:27:19 +02:00
namespace MinecraftClient
{
/// <summary>
/// The main client class, used to connect to a Minecraft server.
/// </summary>
2014-05-31 01:59:03 +02:00
public class McTcpClient : IMinecraftComHandler
2013-07-18 09:27:19 +02:00
{
2015-11-27 17:16:33 +01:00
public static int ReconnectionAttemptsLeft = 0 ;
private static readonly List < string > cmd_names = new List < string > ( ) ;
private static readonly Dictionary < string , Command > cmds = new Dictionary < string , Command > ( ) ;
2016-08-27 15:46:34 +02:00
private readonly Dictionary < Guid , string > onlinePlayers = new Dictionary < Guid , string > ( ) ;
2015-11-27 17:16:33 +01:00
private readonly List < ChatBot > bots = new List < ChatBot > ( ) ;
2017-03-29 21:25:14 +02:00
private static readonly List < ChatBot > botsOnHold = new List < ChatBot > ( ) ;
2020-03-29 18:41:26 +02:00
private static Dictionary < int , Container > inventories = new Dictionary < int , Container > ( ) ;
2014-05-31 01:59:03 +02:00
2016-02-07 14:24:01 -08:00
private readonly Dictionary < string , List < ChatBot > > registeredBotPluginChannels = new Dictionary < string , List < ChatBot > > ( ) ;
private readonly List < string > registeredServerPluginChannels = new List < String > ( ) ;
2019-04-28 21:32:03 +02:00
private bool terrainAndMovementsEnabled ;
private bool terrainAndMovementsRequested = false ;
2019-05-30 11:34:08 +02:00
private bool inventoryHandlingEnabled ;
private bool inventoryHandlingRequested = false ;
2020-03-23 19:59:00 +08:00
private bool entityHandlingEnabled ;
2019-05-30 11:34:08 +02:00
2015-11-30 15:30:49 +01:00
private object locationLock = new object ( ) ;
2015-12-13 21:58:55 +01:00
private bool locationReceived = false ;
2015-11-30 15:30:49 +01:00
private World world = new World ( ) ;
2015-12-12 16:48:29 +01:00
private Queue < Location > steps ;
private Queue < Location > path ;
2015-11-27 17:16:33 +01:00
private Location location ;
2019-04-09 18:01:00 -07:00
private float? yaw ;
private float? pitch ;
2017-03-10 23:40:02 +01:00
private double motionY ;
2013-07-18 09:27:19 +02:00
2014-05-31 01:59:03 +02:00
private string host ;
private int port ;
private string username ;
private string uuid ;
private string sessionid ;
2019-09-15 17:01:53 +02:00
private DateTime lastKeepAlive ;
private object lastKeepAliveLock = new object ( ) ;
2014-05-31 01:59:03 +02:00
2020-03-22 16:04:22 +08:00
private int playerEntityID ;
2020-03-22 23:19:31 +08:00
// not really understand the Inventory Class
// so I use a Dict instead for player inventory
2020-03-26 15:01:42 +08:00
//private Dictionary<int, Inventory.Item> playerItems;
2020-03-22 16:04:22 +08:00
2020-03-23 19:59:00 +08:00
// Entity handling
private Dictionary < int , Entity > entities = new Dictionary < int , Entity > ( ) ;
2020-03-22 16:04:22 +08:00
// server TPS
private long lastAge = 0 ;
private DateTime lastTime ;
private Double serverTPS = 0 ;
2015-06-20 22:58:18 +02:00
public int GetServerPort ( ) { return port ; }
public string GetServerHost ( ) { return host ; }
public string GetUsername ( ) { return username ; }
public string GetUserUUID ( ) { return uuid ; }
public string GetSessionID ( ) { return sessionid ; }
2015-11-27 17:16:33 +01:00
public Location GetCurrentLocation ( ) { return location ; }
2015-11-30 15:30:49 +01:00
public World GetWorld ( ) { return world ; }
2020-03-23 19:59:00 +08:00
public Double GetServerTPS ( ) { return serverTPS ; }
// get bots list for unloading them by commands
public List < ChatBot > GetLoadedChatBots ( )
{
return bots ;
}
2015-05-19 15:36:20 +01:00
2013-07-18 09:27:19 +02:00
TcpClient client ;
2014-05-31 01:59:03 +02:00
IMinecraftCom handler ;
2014-06-19 19:24:03 +02:00
Thread cmdprompt ;
2019-09-15 17:01:53 +02:00
Thread timeoutdetector ;
2013-07-18 09:27:19 +02:00
/// <summary>
2014-05-31 01:59:03 +02:00
/// Starts the main chat client
2013-07-18 09:27:19 +02:00
/// </summary>
/// <param name="username">The chosen username of a premium Minecraft Account</param>
2014-06-13 16:50:55 +02:00
/// <param name="uuid">The player's UUID for online-mode authentication</param>
/// <param name="sessionID">A valid sessionID obtained after logging in</param>
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
2015-10-23 16:54:36 -07:00
public McTcpClient ( string username , string uuid , string sessionID , int protocolversion , ForgeInfo forgeInfo , string server_ip , ushort port )
2013-07-18 09:27:19 +02:00
{
2015-10-23 16:54:36 -07:00
StartClient ( username , uuid , sessionID , server_ip , port , protocolversion , forgeInfo , false , "" ) ;
2013-07-18 09:27:19 +02:00
}
/// <summary>
2014-05-31 01:59:03 +02:00
/// Starts the main chat client in single command sending mode
2013-07-18 09:27:19 +02:00
/// </summary>
/// <param name="username">The chosen username of a premium Minecraft Account</param>
2014-06-13 16:50:55 +02:00
/// <param name="uuid">The player's UUID for online-mode authentication</param>
/// <param name="sessionID">A valid sessionID obtained after logging in</param>
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
2013-07-18 09:27:19 +02:00
/// <param name="command">The text or command to send.</param>
2015-10-23 16:54:36 -07:00
public McTcpClient ( string username , string uuid , string sessionID , string server_ip , ushort port , int protocolversion , ForgeInfo forgeInfo , string command )
2013-07-18 09:27:19 +02:00
{
2015-10-23 16:54:36 -07:00
StartClient ( username , uuid , sessionID , server_ip , port , protocolversion , forgeInfo , true , command ) ;
2013-07-18 09:27:19 +02:00
}
/// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// </summary>
/// <param name="user">The chosen username of a premium Minecraft Account</param>
/// <param name="sessionID">A valid sessionID obtained with MinecraftCom.GetLogin()</param>
2014-06-13 16:50:55 +02:00
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="uuid">The player's UUID for online-mode authentication</param>
2013-07-18 09:27:19 +02:00
/// <param name="singlecommand">If set to true, the client will send a single command and then disconnect from the server</param>
/// <param name="command">The text or command to send. Will only be sent if singlecommand is set to true.</param>
2015-10-23 16:54:36 -07:00
private void StartClient ( string user , string uuid , string sessionID , string server_ip , ushort port , int protocolversion , ForgeInfo forgeInfo , bool singlecommand , string command )
2013-07-18 09:27:19 +02:00
{
2019-04-28 21:32:03 +02:00
terrainAndMovementsEnabled = Settings . TerrainAndMovements ;
2019-05-30 11:34:08 +02:00
inventoryHandlingEnabled = Settings . InventoryHandling ;
2020-03-23 19:59:00 +08:00
entityHandlingEnabled = Settings . EntityHandling ;
2019-04-28 21:32:03 +02:00
2020-03-29 18:41:26 +02:00
if ( inventoryHandlingEnabled )
{
inventories . Clear ( ) ;
inventories [ 0 ] = new Container ( 0 , ContainerType . PlayerInventory , "Player Inventory" ) ;
}
2015-04-06 11:42:43 +02:00
bool retry = false ;
2014-05-31 01:59:03 +02:00
this . sessionid = sessionID ;
this . uuid = uuid ;
this . username = user ;
2014-06-13 16:50:55 +02:00
this . host = server_ip ;
this . port = port ;
2013-07-18 09:27:19 +02:00
2014-05-31 01:59:03 +02:00
if ( ! singlecommand )
{
2017-03-29 21:25:14 +02:00
if ( botsOnHold . Count = = 0 )
{
if ( Settings . AntiAFK_Enabled ) { BotLoad ( new ChatBots . AntiAFK ( Settings . AntiAFK_Delay ) ) ; }
if ( Settings . Hangman_Enabled ) { BotLoad ( new ChatBots . HangmanGame ( Settings . Hangman_English ) ) ; }
if ( Settings . Alerts_Enabled ) { BotLoad ( new ChatBots . Alerts ( ) ) ; }
if ( Settings . ChatLog_Enabled ) { BotLoad ( new ChatBots . ChatLog ( Settings . ExpandVars ( Settings . ChatLog_File ) , Settings . ChatLog_Filter , Settings . ChatLog_DateTime ) ) ; }
if ( Settings . PlayerLog_Enabled ) { BotLoad ( new ChatBots . PlayerListLogger ( Settings . PlayerLog_Delay , Settings . ExpandVars ( Settings . PlayerLog_File ) ) ) ; }
if ( Settings . AutoRelog_Enabled ) { BotLoad ( new ChatBots . AutoRelog ( Settings . AutoRelog_Delay , Settings . AutoRelog_Retries ) ) ; }
if ( Settings . ScriptScheduler_Enabled ) { BotLoad ( new ChatBots . ScriptScheduler ( Settings . ExpandVars ( Settings . ScriptScheduler_TasksFile ) ) ) ; }
if ( Settings . RemoteCtrl_Enabled ) { BotLoad ( new ChatBots . RemoteControl ( ) ) ; }
if ( Settings . AutoRespond_Enabled ) { BotLoad ( new ChatBots . AutoRespond ( Settings . AutoRespond_Matches ) ) ; }
2020-03-23 19:59:00 +08:00
if ( Settings . AutoAttack_Enabled ) { BotLoad ( new ChatBots . AutoAttack ( ) ) ; }
if ( Settings . AutoFishing_Enabled ) { BotLoad ( new ChatBots . AutoFishing ( ) ) ; }
2017-03-29 21:25:14 +02:00
//Add your ChatBot here by uncommenting and adapting
//BotLoad(new ChatBots.YourBot());
}
2014-05-31 01:59:03 +02:00
}
2013-07-18 09:27:19 +02:00
try
{
2015-10-22 20:56:08 +02:00
client = ProxyHandler . newTcpClient ( host , port ) ;
2013-07-18 09:27:19 +02:00
client . ReceiveBufferSize = 1024 * 1024 ;
2016-10-06 19:13:58 +02:00
handler = Protocol . ProtocolHandler . GetProtocolHandler ( client , protocolversion , forgeInfo , this ) ;
2014-05-31 12:56:54 +02:00
Console . WriteLine ( "Version is supported.\nLogging in..." ) ;
2015-04-06 11:42:43 +02:00
try
2013-07-18 09:27:19 +02:00
{
2015-04-06 11:42:43 +02:00
if ( handler . Login ( ) )
2013-07-18 09:27:19 +02:00
{
2015-04-06 11:42:43 +02:00
if ( singlecommand )
{
handler . SendChatMessage ( command ) ;
ConsoleIO . WriteLineFormatted ( "§7Command §8" + command + "§7 sent." ) ;
Thread . Sleep ( 5000 ) ;
handler . Disconnect ( ) ;
Thread . Sleep ( 1000 ) ;
}
else
{
2017-03-29 21:25:14 +02:00
foreach ( ChatBot bot in botsOnHold )
BotLoad ( bot , false ) ;
botsOnHold . Clear ( ) ;
2015-04-06 11:42:43 +02:00
Console . WriteLine ( "Server was successfully joined.\nType '"
+ ( Settings . internalCmdChar = = ' ' ? "" : "" + Settings . internalCmdChar )
+ "quit' to leave the server." ) ;
cmdprompt = new Thread ( new ThreadStart ( CommandPrompt ) ) ;
cmdprompt . Name = "MCC Command prompt" ;
cmdprompt . Start ( ) ;
2019-09-15 17:01:53 +02:00
timeoutdetector = new Thread ( new ThreadStart ( TimeoutDetector ) ) ;
timeoutdetector . Name = "MCC Connection timeout detector" ;
timeoutdetector . Start ( ) ;
2015-04-06 11:42:43 +02:00
}
2013-07-18 09:27:19 +02:00
}
}
2015-04-06 11:42:43 +02:00
catch ( Exception e )
{
ConsoleIO . WriteLineFormatted ( "§8" + e . Message ) ;
Console . WriteLine ( "Failed to join this server." ) ;
retry = true ;
}
2013-07-18 09:27:19 +02:00
}
2015-04-20 17:26:16 +02:00
catch ( SocketException e )
2013-07-18 09:27:19 +02:00
{
2015-04-20 17:26:16 +02:00
ConsoleIO . WriteLineFormatted ( "§8" + e . Message ) ;
2013-07-18 09:27:19 +02:00
Console . WriteLine ( "Failed to connect to this IP." ) ;
2015-04-06 11:42:43 +02:00
retry = true ;
}
if ( retry )
{
2015-11-27 17:16:33 +01:00
if ( ReconnectionAttemptsLeft > 0 )
2013-07-18 09:27:19 +02:00
{
2015-11-27 17:16:33 +01:00
ConsoleIO . WriteLogLine ( "Waiting 5 seconds (" + ReconnectionAttemptsLeft + " attempts left)..." ) ;
2019-05-30 11:45:43 +02:00
Thread . Sleep ( 5000 ) ;
ReconnectionAttemptsLeft - - ;
Program . Restart ( ) ;
2013-07-18 09:27:19 +02:00
}
2015-03-25 22:50:20 +01:00
else if ( ! singlecommand & & Settings . interactiveMode )
{
2015-04-22 10:27:53 +02:00
Program . HandleFailure ( ) ;
2015-03-25 22:50:20 +01:00
}
2013-07-18 09:27:19 +02:00
}
}
/// <summary>
/// Allows the user to send chat messages, commands, and to leave the server.
/// </summary>
2014-06-19 19:24:03 +02:00
private void CommandPrompt ( )
2013-07-18 09:27:19 +02:00
{
try
{
2014-05-31 01:59:03 +02:00
string text = "" ;
Thread . Sleep ( 500 ) ;
2014-01-12 13:38:52 +01:00
handler . SendRespawnPacket ( ) ;
2013-07-18 09:27:19 +02:00
while ( client . Client . Connected )
{
text = ConsoleIO . ReadLine ( ) ;
2018-05-28 22:01:51 +02:00
if ( ConsoleIO . BasicIO & & text . Length > 0 & & text [ 0 ] = = ( char ) 0x00 )
2013-07-18 09:27:19 +02:00
{
2013-08-18 18:26:20 +02:00
//Process a request from the GUI
string [ ] command = text . Substring ( 1 ) . Split ( ( char ) 0x00 ) ;
switch ( command [ 0 ] . ToLower ( ) )
2013-07-18 09:27:19 +02:00
{
2013-08-18 18:26:20 +02:00
case "autocomplete" :
if ( command . Length > 1 ) { ConsoleIO . WriteLine ( ( char ) 0x00 + "autocomplete" + ( char ) 0x00 + handler . AutoComplete ( command [ 1 ] ) ) ; }
else Console . WriteLine ( ( char ) 0x00 + "autocomplete" + ( char ) 0x00 ) ;
break ;
}
}
else
{
2014-01-12 13:38:52 +01:00
text = text . Trim ( ) ;
2014-06-14 13:20:15 +02:00
if ( text . Length > 0 )
2014-01-12 13:38:52 +01:00
{
2014-06-18 00:49:45 +02:00
if ( Settings . internalCmdChar = = ' ' | | text [ 0 ] = = Settings . internalCmdChar )
2013-07-18 09:27:19 +02:00
{
2014-06-14 18:48:43 +02:00
string response_msg = "" ;
2014-06-18 00:49:45 +02:00
string command = Settings . internalCmdChar = = ' ' ? text : text . Substring ( 1 ) ;
2015-06-20 22:58:18 +02:00
if ( ! PerformInternalCommand ( Settings . ExpandVars ( command ) , ref response_msg ) & & Settings . internalCmdChar = = '/' )
2013-07-18 09:27:19 +02:00
{
2014-06-19 19:24:03 +02:00
SendText ( text ) ;
2014-06-14 18:48:43 +02:00
}
else if ( response_msg . Length > 0 )
{
2014-06-18 00:49:45 +02:00
ConsoleIO . WriteLineFormatted ( "§8MCC: " + response_msg ) ;
2013-07-18 09:27:19 +02:00
}
}
2014-06-19 19:24:03 +02:00
else SendText ( text ) ;
2013-07-18 09:27:19 +02:00
}
}
}
}
catch ( IOException ) { }
2015-07-30 17:32:42 +02:00
catch ( NullReferenceException ) { }
2013-07-18 09:27:19 +02:00
}
2019-09-15 17:01:53 +02:00
/// <summary>
/// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old.
/// </summary>
private void TimeoutDetector ( )
{
lock ( lastKeepAliveLock )
{
lastKeepAlive = DateTime . Now ;
}
do
{
Thread . Sleep ( TimeSpan . FromSeconds ( 15 ) ) ;
lock ( lastKeepAliveLock )
{
if ( lastKeepAlive . AddSeconds ( 30 ) < DateTime . Now )
{
OnConnectionLost ( ChatBot . DisconnectReason . ConnectionLost , "Connection Timeout" ) ;
}
}
}
while ( true ) ;
}
2014-06-14 13:20:15 +02:00
/// <summary>
2014-06-19 19:24:03 +02:00
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
2014-06-14 13:20:15 +02:00
/// </summary>
/// <param name="command">The command</param>
2014-06-14 18:48:43 +02:00
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
2020-03-27 21:14:05 +01:00
/// <param name="localVars">Local variables passed along with the command</param>
2014-06-14 18:48:43 +02:00
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
2020-03-27 21:14:05 +01:00
public bool PerformInternalCommand ( string command , ref string response_msg , Dictionary < string , object > localVars = null )
2014-06-14 13:20:15 +02:00
{
2014-06-18 13:32:17 +02:00
/* Load commands from the 'Commands' namespace */
2014-06-14 13:20:15 +02:00
2014-06-18 13:32:17 +02:00
if ( cmds . Count = = 0 )
{
Type [ ] cmds_classes = Program . GetTypesInNamespace ( "MinecraftClient.Commands" ) ;
foreach ( Type type in cmds_classes )
{
if ( type . IsSubclassOf ( typeof ( Command ) ) )
2014-06-14 18:48:43 +02:00
{
2014-06-18 13:32:17 +02:00
try
2014-06-14 18:48:43 +02:00
{
2014-06-18 13:32:17 +02:00
Command cmd = ( Command ) Activator . CreateInstance ( type ) ;
cmds [ cmd . CMDName . ToLower ( ) ] = cmd ;
cmd_names . Add ( cmd . CMDName . ToLower ( ) ) ;
foreach ( string alias in cmd . getCMDAliases ( ) )
cmds [ alias . ToLower ( ) ] = cmd ;
}
catch ( Exception e )
{
ConsoleIO . WriteLine ( e . Message ) ;
2014-06-14 18:48:43 +02:00
}
}
2014-06-18 13:32:17 +02:00
}
}
2014-06-14 13:20:15 +02:00
2014-06-18 13:32:17 +02:00
/* Process the provided command */
2014-06-14 13:20:15 +02:00
2014-06-18 13:32:17 +02:00
string command_name = command . Split ( ' ' ) [ 0 ] . ToLower ( ) ;
if ( command_name = = "help" )
{
if ( Command . hasArg ( command ) )
{
string help_cmdname = Command . getArgs ( command ) [ 0 ] . ToLower ( ) ;
if ( help_cmdname = = "help" )
2014-06-14 18:48:43 +02:00
{
2014-06-18 13:32:17 +02:00
response_msg = "help <cmdname>: show brief help about a command." ;
2014-06-14 18:48:43 +02:00
}
2014-06-18 13:32:17 +02:00
else if ( cmds . ContainsKey ( help_cmdname ) )
2014-06-14 18:48:43 +02:00
{
2014-06-18 13:32:17 +02:00
response_msg = cmds [ help_cmdname ] . CMDDesc ;
2014-06-14 18:48:43 +02:00
}
2014-06-18 13:32:17 +02:00
else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list." ;
}
2017-03-09 21:13:52 +01:00
else response_msg = "help <cmdname>. Available commands: " + String . Join ( ", " , cmd_names . ToArray ( ) ) + ". For server help, use '" + Settings . internalCmdChar + "send /help' instead." ;
2014-06-18 13:32:17 +02:00
}
else if ( cmds . ContainsKey ( command_name ) )
{
2020-03-27 21:14:05 +01:00
response_msg = cmds [ command_name ] . Run ( this , command , localVars ) ;
2014-06-18 13:32:17 +02:00
}
else
{
2014-08-18 15:10:15 +02:00
response_msg = "Unknown command '" + command_name + "'. Use '" + ( Settings . internalCmdChar = = ' ' ? "" : "" + Settings . internalCmdChar ) + "help' for help." ;
2014-06-18 13:32:17 +02:00
return false ;
2014-06-14 13:20:15 +02:00
}
return true ;
}
2013-07-18 09:27:19 +02:00
/// <summary>
2019-12-08 22:24:20 +01:00
/// Disconnect the client from the server (initiated from MCC)
2013-07-18 09:27:19 +02:00
/// </summary>
2014-05-31 01:59:03 +02:00
public void Disconnect ( )
2013-07-18 09:27:19 +02:00
{
2019-12-08 20:18:40 +01:00
foreach ( ChatBot bot in bots . ToArray ( ) )
2019-12-08 22:24:20 +01:00
bot . OnDisconnect ( ChatBot . DisconnectReason . UserLogout , "" ) ;
2019-09-22 13:56:59 +02:00
2017-03-29 21:25:14 +02:00
botsOnHold . Clear ( ) ;
botsOnHold . AddRange ( bots ) ;
2014-05-31 01:59:03 +02:00
2015-03-02 21:35:45 +01:00
if ( handler ! = null )
{
handler . Disconnect ( ) ;
handler . Dispose ( ) ;
}
2014-06-19 19:24:03 +02:00
if ( cmdprompt ! = null )
cmdprompt . Abort ( ) ;
2019-09-15 17:01:53 +02:00
if ( timeoutdetector ! = null )
2019-11-24 12:49:03 +01:00
{
2019-09-15 17:01:53 +02:00
timeoutdetector . Abort ( ) ;
2019-11-24 12:49:03 +01:00
timeoutdetector = null ;
}
2019-09-15 17:01:53 +02:00
2014-05-31 01:59:03 +02:00
Thread . Sleep ( 1000 ) ;
2015-06-20 22:58:18 +02:00
if ( client ! = null )
client . Close ( ) ;
2014-05-31 01:59:03 +02:00
}
2013-07-18 09:27:19 +02:00
2015-09-30 20:01:57 +02:00
/// <summary>
2016-08-22 19:09:43 +02:00
/// Load a new bot
/// </summary>
2017-03-29 21:25:14 +02:00
public void BotLoad ( ChatBot b , bool init = true )
2016-08-22 19:09:43 +02:00
{
b . SetHandler ( this ) ;
bots . Add ( b ) ;
2017-03-29 21:25:14 +02:00
if ( init )
b . Initialize ( ) ;
2016-08-22 19:09:43 +02:00
if ( this . handler ! = null )
b . AfterGameJoined ( ) ;
Settings . SingleCommand = "" ;
}
/// <summary>
/// Unload a bot
/// </summary>
public void BotUnLoad ( ChatBot b )
{
bots . RemoveAll ( item = > object . ReferenceEquals ( item , b ) ) ;
// ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon.
var botRegistrations = registeredBotPluginChannels . Where ( entry = > entry . Value . Contains ( b ) ) . ToList ( ) ;
foreach ( var entry in botRegistrations )
{
UnregisterPluginChannel ( entry . Key , b ) ;
}
}
/// <summary>
/// Clear bots
2015-09-30 20:01:57 +02:00
/// </summary>
2016-08-22 19:09:43 +02:00
public void BotClear ( )
{
bots . Clear ( ) ;
}
2015-09-30 20:01:57 +02:00
2016-08-22 19:09:43 +02:00
/// <summary>
/// Called when a server was successfully joined
/// </summary>
2015-09-30 20:01:57 +02:00
public void OnGameJoined ( )
{
if ( ! String . IsNullOrWhiteSpace ( Settings . BrandInfo ) )
handler . SendBrandInfo ( Settings . BrandInfo . Trim ( ) ) ;
2016-08-26 12:19:25 +02:00
if ( Settings . MCSettings_Enabled )
handler . SendClientSettings (
Settings . MCSettings_Locale ,
Settings . MCSettings_RenderDistance ,
Settings . MCSettings_Difficulty ,
Settings . MCSettings_ChatMode ,
Settings . MCSettings_ChatColors ,
Settings . MCSettings_Skin_All ,
Settings . MCSettings_MainHand ) ;
2019-12-08 20:18:40 +01:00
foreach ( ChatBot bot in bots . ToArray ( ) )
2016-02-07 14:47:03 -08:00
bot . AfterGameJoined ( ) ;
2019-05-30 11:34:08 +02:00
if ( inventoryHandlingRequested )
{
inventoryHandlingRequested = false ;
inventoryHandlingEnabled = true ;
ConsoleIO . WriteLogLine ( "Inventory handling is now enabled." ) ;
}
2015-09-30 20:01:57 +02:00
}
2019-04-28 21:32:03 +02:00
/// <summary>
/// Called when the player respawns, which happens on login, respawn and world change.
/// </summary>
public void OnRespawn ( )
{
if ( terrainAndMovementsRequested )
{
terrainAndMovementsEnabled = true ;
terrainAndMovementsRequested = false ;
ConsoleIO . WriteLogLine ( "Terrain and Movements is now enabled." ) ;
}
if ( terrainAndMovementsEnabled )
{
world . Clear ( ) ;
}
}
/// <summary>
/// Get Terrain and Movements status.
/// </summary>
public bool GetTerrainEnabled ( )
{
return terrainAndMovementsEnabled ;
}
2019-05-26 10:36:46 -04:00
/// <summary>
/// Get Inventory Handling Mode
/// </summary>
public bool GetInventoryEnabled ( )
{
2019-05-30 11:34:08 +02:00
return inventoryHandlingEnabled ;
2019-05-26 10:36:46 -04:00
}
2019-04-28 21:32:03 +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 )
{
if ( enabled )
{
if ( ! terrainAndMovementsEnabled )
{
terrainAndMovementsRequested = true ;
return false ;
}
}
else
{
terrainAndMovementsEnabled = false ;
terrainAndMovementsRequested = false ;
locationReceived = false ;
world . Clear ( ) ;
}
return true ;
}
2019-05-30 11:34:08 +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 )
{
if ( enabled )
{
if ( ! inventoryHandlingEnabled )
{
inventoryHandlingRequested = true ;
return false ;
}
}
else
{
inventoryHandlingEnabled = false ;
inventoryHandlingRequested = false ;
inventories . Clear ( ) ;
}
return true ;
}
2020-03-23 19:59:00 +08: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-03-28 00:48:41 +01: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>
2020-03-26 19:10:28 +08:00
public bool SetEntityHandlingEnabled ( bool enabled )
{
if ( ! enabled )
{
if ( entityHandlingEnabled )
{
entityHandlingEnabled = false ;
return true ;
}
else
{
return false ;
}
}
else
{
// Entity Handling cannot be enabled in runtime (or after joining server)
return false ;
}
}
2020-03-29 18:41:26 +02:00
/// <summary>
/// Get all inventories. ID 0 is the player inventory.
/// </summary>
/// <returns>All inventories</returns>
public Dictionary < int , Container > GetInventories ( )
{
return inventories ;
}
/// <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>
public Container GetInventory ( int inventoryID )
{
if ( inventories . ContainsKey ( inventoryID ) )
return inventories [ inventoryID ] ;
return null ;
}
2020-03-23 19:59:00 +08:00
/// <summary>
/// Get client player's inventory items
/// </summary>
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
2020-03-26 15:01:42 +08:00
public Container GetPlayerInventory ( )
2020-03-23 19:59:00 +08:00
{
2020-03-29 18:41:26 +02:00
return GetInventory ( 0 ) ;
2020-03-23 19:59:00 +08:00
}
2015-11-27 17:16:33 +01: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>
public void UpdateLocation ( Location location , bool relative )
{
2015-11-30 15:30:49 +01:00
lock ( locationLock )
2015-11-27 17:16:33 +01:00
{
2015-11-30 15:30:49 +01:00
if ( relative )
{
this . location + = location ;
}
else this . location = location ;
2015-12-13 21:58:55 +01:00
locationReceived = true ;
2015-11-27 17:16:33 +01: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>
2019-04-12 17:08:30 +02:00
/// <param name="yaw">Yaw to look at</param>
/// <param name="pitch">Pitch to look at</param>
2019-04-09 18:01:00 -07:00
public void UpdateLocation ( Location location , float yaw , float pitch )
2015-11-27 17:16:33 +01:00
{
2019-04-09 18:01:00 -07:00
this . yaw = yaw ;
this . pitch = pitch ;
2015-11-27 17:16:33 +01:00
UpdateLocation ( location , false ) ;
}
2019-04-09 18:01:00 -07:00
/// <summary>
2019-04-12 17:08:30 +02:00
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
2019-04-09 18:01:00 -07:00
/// </summary>
2019-04-12 17:08:30 +02:00
/// <param name="location">The new location</param>
2019-10-03 09:48:40 +02:00
/// <param name="lookAtLocation">Block coordinates to look at</param>
2019-04-12 17:08:30 +02:00
public void UpdateLocation ( Location location , Location lookAtLocation )
2019-04-09 18:01:00 -07:00
{
2019-04-12 17:08:30 +02:00
double dx = lookAtLocation . X - ( location . X - 0.5 ) ;
double dy = lookAtLocation . Y - ( location . Y + 1 ) ;
double dz = lookAtLocation . Z - ( location . Z - 0.5 ) ;
2019-04-09 18:01:00 -07:00
double r = Math . Sqrt ( dx * dx + dy * dy + dz * dz ) ;
2019-04-12 17:08:30 +02:00
float yaw = Convert . ToSingle ( - Math . Atan2 ( dx , dz ) / Math . PI * 180 ) ;
float pitch = Convert . ToSingle ( - Math . Asin ( dy / r ) / Math . PI * 180 ) ;
2019-04-09 18:01:00 -07:00
if ( yaw < 0 ) yaw + = 360 ;
2019-04-09 20:19:27 -07:00
UpdateLocation ( location , yaw , pitch ) ;
}
2019-04-09 18:01:00 -07:00
2019-04-09 20:19:27 -07:00
/// <summary>
2019-04-12 17:08:30 +02:00
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
2019-04-09 20:19:27 -07:00
/// </summary>
2019-04-12 17:08:30 +02:00
/// <param name="location">The new location</param>
/// <param name="direction">Direction to look at</param>
public void UpdateLocation ( Location location , Direction direction )
2019-04-09 20:19:27 -07:00
{
2019-04-12 17:08:30 +02:00
float yaw = 0 ;
float pitch = 0 ;
2019-04-09 18:01:00 -07:00
2019-04-09 20:19:27 -07: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 :
2019-04-13 08:28:59 +02:00
yaw = 180 ;
2019-04-09 20:19:27 -07:00
break ;
case Direction . South :
break ;
default :
throw new ArgumentException ( "Unknown direction" , "direction" ) ;
}
2019-04-12 17:08:30 +02:00
2019-04-09 20:19:27 -07:00
UpdateLocation ( location , yaw , pitch ) ;
}
2019-04-09 18:01:00 -07:00
2015-12-12 16:48:29 +01:00
/// <summary>
/// Move to the specified location
/// </summary>
/// <param name="location">Location to reach</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations</param>
/// <returns>True if a path has been found</returns>
public bool MoveTo ( Location location , bool allowUnsafe = false )
{
lock ( locationLock )
{
if ( Movement . GetAvailableMoves ( world , this . location , allowUnsafe ) . Contains ( location ) )
path = new Queue < Location > ( new [ ] { location } ) ;
else path = Movement . CalculatePath ( world , this . location , location , allowUnsafe ) ;
return path ! = null ;
}
}
2014-05-31 01:59:03 +02:00
/// <summary>
/// Received some text from the server
/// </summary>
/// <param name="text">Text received</param>
2017-05-31 20:54:16 +02:00
/// <param name="isJson">TRUE if the text is JSON-Encoded</param>
public void OnTextReceived ( string text , bool isJson )
2014-05-31 01:59:03 +02:00
{
2019-09-15 17:01:53 +02:00
lock ( lastKeepAliveLock )
{
lastKeepAlive = DateTime . Now ;
}
2017-05-31 20:54:16 +02:00
List < string > links = new List < string > ( ) ;
string json = null ;
if ( isJson )
{
json = text ;
text = ChatParser . ParseText ( json , links ) ;
}
2017-06-07 18:58:21 +02:00
ConsoleIO . WriteLineFormatted ( text , true ) ;
2016-10-07 19:52:28 +02:00
if ( Settings . DisplayChatLinks )
foreach ( string link in links )
ConsoleIO . WriteLineFormatted ( "§8MCC: Link: " + link , false ) ;
2018-05-23 19:19:27 +02:00
foreach ( ChatBot bot in bots . ToArray ( ) )
2015-03-23 13:57:31 +01:00
{
try
{
2018-05-23 19:19:27 +02:00
bot . GetText ( text ) ;
if ( bots . Contains ( bot ) )
bot . GetText ( text , json ) ;
2015-03-23 13:57:31 +01:00
}
catch ( Exception e )
{
if ( ! ( e is ThreadAbortException ) )
{
2018-05-23 19:19:27 +02:00
ConsoleIO . WriteLineFormatted ( "§8GetText: Got error from " + bot . ToString ( ) + ": " + e . ToString ( ) ) ;
2015-03-23 13:57:31 +01:00
}
else throw ; //ThreadAbortException should not be caught
}
}
2014-05-31 01:59:03 +02:00
}
2019-09-15 17:01:53 +02:00
/// <summary>
/// Received a connection keep-alive from the server
/// </summary>
public void OnServerKeepAlive ( )
{
lock ( lastKeepAliveLock )
{
lastKeepAlive = DateTime . Now ;
}
}
2019-05-25 17:45:59 -04:00
/// <summary>
/// When an inventory is opened
/// </summary>
/// <param name="inventory">Location to reach</param>
2020-03-29 18:41:26 +02:00
public void OnInventoryOpen ( int inventoryID , Container inventory )
2019-05-25 17:45:59 -04:00
{
2020-03-29 18:41:26 +02:00
inventories [ inventoryID ] = inventory ;
2020-03-28 15:01:08 +01:00
2020-03-29 18:41:26 +02:00
if ( inventoryID ! = 0 )
2020-03-28 15:01:08 +01:00
{
2020-03-29 18:41:26 +02:00
ConsoleIO . WriteLogLine ( "Inventory # " + inventoryID + " opened: " + inventory . Title ) ;
ConsoleIO . WriteLogLine ( "Use /inventory to interact with it." ) ;
2020-03-28 15:01:08 +01:00
}
2019-05-26 10:36:46 -04:00
}
/// <summary>
/// When an inventory is close
/// </summary>
/// <param name="inventoryID">Location to reach</param>
2020-03-29 18:41:26 +02:00
public void OnInventoryClose ( int inventoryID )
2019-05-26 10:36:46 -04:00
{
2020-03-29 18:41:26 +02:00
if ( inventories . ContainsKey ( inventoryID ) )
inventories . Remove ( inventoryID ) ;
if ( inventoryID ! = 0 )
ConsoleIO . WriteLogLine ( "Inventory # " + inventoryID + " closed." ) ;
2019-05-25 17:45:59 -04:00
}
2020-03-22 23:19:31 +08:00
/// <summary>
/// When received window items from server.
/// </summary>
2020-03-29 18:41:26 +02:00
/// <param name="inventoryID">Inventory ID</param>
2020-03-28 15:01:08 +01:00
/// <param name="itemList">Item list, key = slot ID, value = Item information</param>
2020-03-29 18:41:26 +02:00
public void OnWindowItems ( byte inventoryID , Dictionary < int , Inventory . Item > itemList )
2020-03-22 23:19:31 +08:00
{
2020-03-29 18:41:26 +02:00
if ( inventories . ContainsKey ( inventoryID ) )
inventories [ inventoryID ] . Items = itemList ;
2020-03-26 15:01:42 +08:00
}
2020-03-28 00:48:41 +01:00
/// <summary>
/// When a slot is set inside window items
/// </summary>
2020-03-29 18:41:26 +02:00
/// <param name="inventoryID">Window ID</param>
/// <param name="slotID">Slot ID</param>
/// <param name="item">Item (may be null for empty slot)</param>
public void OnSetSlot ( byte inventoryID , short slotID , Item item )
2020-03-26 15:01:42 +08:00
{
2020-03-29 18:41:26 +02:00
if ( inventories . ContainsKey ( inventoryID ) )
2020-03-26 15:01:42 +08:00
{
2020-03-29 18:41:26 +02:00
if ( item = = null | | item . IsEmpty )
{
if ( inventories [ inventoryID ] . Items . ContainsKey ( slotID ) )
inventories [ inventoryID ] . Items . Remove ( slotID ) ;
}
else inventories [ inventoryID ] . Items [ slotID ] = item ;
2020-03-26 15:01:42 +08:00
}
2020-03-22 23:19:31 +08:00
}
2014-05-31 01:59:03 +02:00
/// <summary>
2019-12-08 22:24:20 +01:00
/// When connection has been lost, login was denied or played was kicked from the server
2014-05-31 01:59:03 +02:00
/// </summary>
public void OnConnectionLost ( ChatBot . DisconnectReason reason , string message )
{
2019-04-28 21:32:03 +02:00
world . Clear ( ) ;
2019-11-24 12:49:03 +01:00
if ( timeoutdetector ! = null )
{
timeoutdetector . Abort ( ) ;
timeoutdetector = null ;
}
2014-05-31 01:59:03 +02:00
bool will_restart = false ;
switch ( reason )
2013-07-18 09:27:19 +02:00
{
2014-05-31 01:59:03 +02:00
case ChatBot . DisconnectReason . ConnectionLost :
message = "Connection has been lost." ;
ConsoleIO . WriteLine ( message ) ;
break ;
case ChatBot . DisconnectReason . InGameKick :
ConsoleIO . WriteLine ( "Disconnected by Server :" ) ;
2014-06-11 20:40:25 +02:00
ConsoleIO . WriteLineFormatted ( message ) ;
2014-05-31 01:59:03 +02:00
break ;
case ChatBot . DisconnectReason . LoginRejected :
ConsoleIO . WriteLine ( "Login failed :" ) ;
2014-06-11 20:40:25 +02:00
ConsoleIO . WriteLineFormatted ( message ) ;
2014-05-31 01:59:03 +02:00
break ;
2019-12-08 22:24:20 +01:00
case ChatBot . DisconnectReason . UserLogout :
throw new InvalidOperationException ( "User-initiated logout should be done by calling Disconnect()" ) ;
2013-07-18 09:27:19 +02:00
}
2014-05-31 01:59:03 +02:00
2019-12-08 20:18:40 +01:00
foreach ( ChatBot bot in bots . ToArray ( ) )
2014-05-31 01:59:03 +02:00
will_restart | = bot . OnDisconnect ( reason , message ) ;
2015-04-20 17:26:16 +02:00
if ( ! will_restart )
2015-04-22 10:27:53 +02:00
Program . HandleFailure ( ) ;
2013-07-18 09:27:19 +02:00
}
/// <summary>
2014-05-31 01:59:03 +02:00
/// Called ~10 times per second by the protocol handler
2013-07-18 09:27:19 +02:00
/// </summary>
2014-05-31 01:59:03 +02:00
public void OnUpdate ( )
2013-07-18 09:27:19 +02:00
{
2016-02-23 11:19:14 -07:00
foreach ( var bot in bots . ToArray ( ) )
2014-06-11 20:40:25 +02:00
{
try
{
2016-02-23 11:19:14 -07:00
bot . Update ( ) ;
bot . ProcessQueuedText ( ) ;
2014-06-11 20:40:25 +02:00
}
catch ( Exception e )
{
2014-09-07 15:11:39 +02:00
if ( ! ( e is ThreadAbortException ) )
{
2016-02-23 11:19:14 -07:00
ConsoleIO . WriteLineFormatted ( "§8Update: Got error from " + bot . ToString ( ) + ": " + e . ToString ( ) ) ;
2014-09-07 15:11:39 +02:00
}
else throw ; //ThreadAbortException should not be caught
2014-06-11 20:40:25 +02:00
}
}
2015-11-27 17:16:33 +01:00
2019-04-28 21:32:03 +02:00
if ( terrainAndMovementsEnabled & & locationReceived )
2015-11-27 17:16:33 +01:00
{
2015-12-09 23:04:00 +01:00
lock ( locationLock )
2015-11-27 17:16:33 +01:00
{
2015-12-12 16:48:29 +01:00
for ( int i = 0 ; i < 2 ; i + + ) //Needs to run at 20 tps; MCC runs at 10 tps
{
2019-04-09 18:01:00 -07:00
if ( yaw = = null | | pitch = = null )
2017-03-10 23:40:02 +01:00
{
if ( steps ! = null & & steps . Count > 0 )
2019-04-12 17:08:30 +02:00
{
2017-03-10 23:40:02 +01:00
location = steps . Dequeue ( ) ;
2019-04-12 17:08:30 +02:00
}
2017-03-10 23:40:02 +01:00
else if ( path ! = null & & path . Count > 0 )
2019-04-12 17:08:30 +02:00
{
Location next = path . Dequeue ( ) ;
steps = Movement . Move2Steps ( location , next , ref motionY ) ;
2019-04-13 08:28:59 +02:00
UpdateLocation ( location , next + new Location ( 0 , 1 , 0 ) ) ; // Update yaw and pitch to look at next step
2019-04-12 17:08:30 +02:00
}
else
{
location = Movement . HandleGravity ( world , location , ref motionY ) ;
}
2017-03-10 23:40:02 +01:00
}
2019-04-09 18:01:00 -07:00
handler . SendLocationUpdate ( location , Movement . IsOnGround ( world , location ) , yaw , pitch ) ;
2015-12-12 16:48:29 +01:00
}
2019-04-12 17:08:30 +02: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)
2019-04-09 18:01:00 -07:00
yaw = null ;
pitch = null ;
2015-11-27 17:16:33 +01:00
}
}
2014-05-31 01:59:03 +02:00
}
/// <summary>
2014-06-14 13:20:15 +02:00
/// Send a chat message or command to the server
2014-05-31 01:59:03 +02:00
/// </summary>
2014-06-14 13:20:15 +02:00
/// <param name="text">Text to send to the server</param>
/// <returns>True if the text was sent with no error</returns>
2014-06-19 19:24:03 +02:00
public bool SendText ( string text )
2014-05-31 01:59:03 +02:00
{
2016-11-19 16:06:08 +01:00
int maxLength = handler . GetMaxChatMessageLength ( ) ;
if ( text . Length > maxLength ) //Message is too long?
2014-06-14 13:20:15 +02:00
{
if ( text [ 0 ] = = '/' )
{
2016-11-19 16:06:08 +01:00
//Send the first 100/256 chars of the command
text = text . Substring ( 0 , maxLength ) ;
2014-06-14 13:20:15 +02:00
return handler . SendChatMessage ( text ) ;
}
else
{
//Send the message splitted into several messages
2016-11-19 16:06:08 +01:00
while ( text . Length > maxLength )
2014-06-14 13:20:15 +02:00
{
2016-11-19 16:06:08 +01:00
handler . SendChatMessage ( text . Substring ( 0 , maxLength ) ) ;
text = text . Substring ( maxLength , text . Length - maxLength ) ;
2015-05-26 19:00:37 +02:00
if ( Settings . splitMessageDelay . TotalSeconds > 0 )
Thread . Sleep ( Settings . splitMessageDelay ) ;
2014-06-14 13:20:15 +02:00
}
return handler . SendChatMessage ( text ) ;
}
}
else return handler . SendChatMessage ( text ) ;
2013-07-18 09:27:19 +02:00
}
2014-06-18 13:32:17 +02:00
2014-11-11 00:55:42 +11:00
/// <summary>
2014-11-10 20:43:00 +01:00
/// Allow to respawn after death
2014-11-11 00:55:42 +11:00
/// </summary>
2014-11-10 20:43:00 +01:00
/// <returns>True if packet successfully sent</returns>
public bool SendRespawnPacket ( )
2014-11-11 00:55:42 +11:00
{
2014-11-10 20:43:00 +01:00
return handler . SendRespawnPacket ( ) ;
2014-11-11 00:55:42 +11:00
}
2014-11-11 00:32:32 +11:00
2014-06-18 13:32:17 +02:00
/// <summary>
2014-11-10 20:43:00 +01:00
/// Triggered when a new player joins the game
2014-06-18 13:32:17 +02:00
/// </summary>
2014-11-10 20:43:00 +01:00
/// <param name="uuid">UUID of the player</param>
2016-08-27 15:46:34 +02:00
/// <param name="name">Name of the player</param>
public void OnPlayerJoin ( Guid uuid , string name )
2014-11-11 00:55:42 +11:00
{
2016-08-22 23:15:16 +02:00
//Ignore placeholders eg 0000tab# from TabListPlus
2016-08-27 15:46:34 +02:00
if ( ! ChatBot . IsValidName ( name ) )
2015-04-06 11:42:43 +02:00
return ;
2015-03-19 22:08:26 +01:00
lock ( onlinePlayers )
{
2016-08-27 15:46:34 +02:00
onlinePlayers [ uuid ] = name ;
2015-03-19 22:08:26 +01:00
}
2014-11-11 00:55:42 +11:00
}
2015-05-19 15:36:20 +01:00
2014-11-10 20:43:00 +01:00
/// <summary>
/// Triggered when a player has left the game
/// </summary>
/// <param name="uuid">UUID of the player</param>
public void OnPlayerLeave ( Guid uuid )
{
2015-03-19 22:08:26 +01:00
lock ( onlinePlayers )
{
onlinePlayers . Remove ( uuid ) ;
}
2014-11-11 00:55:42 +11:00
}
2014-11-10 20:43:00 +01:00
/// <summary>
/// Get a set of online player names
/// </summary>
/// <returns>Online player names</returns>
2016-08-27 15:46:34 +02:00
public string [ ] GetOnlinePlayers ( )
2014-11-10 20:43:00 +01:00
{
2015-03-19 22:08:26 +01:00
lock ( onlinePlayers )
{
return onlinePlayers . Values . Distinct ( ) . ToArray ( ) ;
}
2014-11-10 20:43:00 +01:00
}
2016-02-07 14:24:01 -08:00
2019-03-30 10:47:59 -04:00
/// <summary>
/// Get a dictionary of online player names and their corresponding UUID
/// </summary>
2020-03-28 15:01:08 +01:00
/// <returns>Dictionay of online players, key is UUID, value is Player name</returns>
2019-03-30 10:47:59 -04:00
public Dictionary < string , string > GetOnlinePlayersWithUUID ( )
{
Dictionary < string , string > uuid2Player = new Dictionary < string , string > ( ) ;
lock ( onlinePlayers )
{
foreach ( Guid key in onlinePlayers . Keys )
{
uuid2Player . Add ( key . ToString ( ) , onlinePlayers [ key ] ) ;
}
}
return uuid2Player ;
}
2016-02-07 14:24:01 -08: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>
public void RegisterPluginChannel ( string channel , ChatBot bot )
{
if ( registeredBotPluginChannels . ContainsKey ( channel ) )
{
registeredBotPluginChannels [ channel ] . Add ( bot ) ;
}
else
{
List < ChatBot > bots = new List < ChatBot > ( ) ;
bots . Add ( bot ) ;
registeredBotPluginChannels [ channel ] = bots ;
SendPluginChannelMessage ( "REGISTER" , Encoding . UTF8 . GetBytes ( channel ) , true ) ;
}
}
/// <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>
public void UnregisterPluginChannel ( string channel , ChatBot bot )
{
if ( registeredBotPluginChannels . ContainsKey ( channel ) )
{
List < ChatBot > registeredBots = registeredBotPluginChannels [ channel ] ;
registeredBots . RemoveAll ( item = > object . ReferenceEquals ( item , bot ) ) ;
if ( registeredBots . Count = = 0 )
{
registeredBotPluginChannels . Remove ( channel ) ;
SendPluginChannelMessage ( "UNREGISTER" , Encoding . UTF8 . GetBytes ( channel ) , true ) ;
}
}
}
/// <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>
public bool SendPluginChannelMessage ( string channel , byte [ ] data , bool sendEvenIfNotRegistered = false )
{
if ( ! sendEvenIfNotRegistered )
{
if ( ! registeredBotPluginChannels . ContainsKey ( channel ) )
{
return false ;
}
if ( ! registeredServerPluginChannels . Contains ( channel ) )
{
return false ;
}
}
return handler . SendPluginChannelPacket ( channel , data ) ;
}
/// <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 ) ;
}
}
if ( registeredBotPluginChannels . ContainsKey ( channel ) )
{
foreach ( ChatBot bot in registeredBotPluginChannels [ channel ] )
{
bot . OnPluginMessage ( channel , data ) ;
}
}
}
2020-03-23 19:59:00 +08:00
/// <summary>
/// Called when a non-living entity spawned (fishing hook, minecart, etc)
/// </summary>
/// <param name="EntityID"></param>
2020-03-26 15:01:42 +08:00
/// <param name="TypeID"></param>
2020-03-23 19:59:00 +08:00
/// <param name="UUID"></param>
/// <param name="location"></param>
2020-03-26 15:01:42 +08:00
public void OnSpawnEntity ( int EntityID , int TypeID , Guid UUID , Location location )
2020-03-22 20:12:06 +08:00
{
2020-03-26 19:10:28 +08:00
if ( entities . ContainsKey ( EntityID ) ) return ;
2020-03-26 15:01:42 +08:00
Entity entity = new Entity ( EntityID , TypeID , EntityType . NonLivingThings , location ) ;
2020-03-23 19:59:00 +08:00
entities . Add ( EntityID , entity ) ;
foreach ( ChatBot bot in bots . ToArray ( ) )
bot . OnEntitySpawn ( entity ) ;
2020-03-22 20:12:06 +08:00
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Called when an Entity was created/spawned.
/// </summary>
/// <param name="EntityID"></param>
2020-03-26 15:01:42 +08:00
/// <param name="TypeID"></param>
2020-03-21 21:44:33 +08:00
/// <param name="UUID"></param>
/// <param name="location"></param>
2020-03-26 15:01:42 +08:00
/// <remarks>Cannot determine is a Mob or a Cuty Animal</remarks>
public void OnSpawnLivingEntity ( int EntityID , int TypeID , Guid UUID , Location location )
2020-03-21 18:41:48 +08:00
{
2020-03-26 19:10:28 +08:00
if ( entities . ContainsKey ( EntityID ) ) return ;
2020-03-26 15:01:42 +08:00
Entity entity = new Entity ( EntityID , TypeID , EntityType . MobAndAnimal , location ) ;
entities . Add ( EntityID , entity ) ;
foreach ( ChatBot bot in bots . ToArray ( ) )
bot . OnEntitySpawn ( entity ) ;
}
/// <summary>
/// Called when a player was spawned/in the render distance
/// </summary>
/// <param name="EntityID"></param>
/// <param name="UUID"></param>
/// <param name="location"></param>
/// <param name="Yaw"></param>
/// <param name="Pitch"></param>
public void OnSpawnPlayer ( int EntityID , Guid UUID , Location location , byte Yaw , byte Pitch )
{
2020-03-26 19:10:28 +08:00
if ( entities . ContainsKey ( EntityID ) ) return ;
2020-03-26 15:01:42 +08:00
Entity entity = new Entity ( EntityID , EntityType . Player , location ) ;
2020-03-23 19:59:00 +08:00
entities . Add ( EntityID , entity ) ;
foreach ( ChatBot bot in bots . ToArray ( ) )
bot . OnEntitySpawn ( entity ) ;
2020-03-21 18:41:48 +08:00
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Called when entities dead/despawn.
/// </summary>
/// <param name="Entities"></param>
2020-03-21 18:41:48 +08:00
public void OnDestroyEntities ( int [ ] Entities )
{
2020-03-21 21:44:33 +08:00
foreach ( int a in Entities )
2020-03-21 18:41:48 +08:00
{
2020-03-23 19:59:00 +08:00
if ( entities . ContainsKey ( a ) )
2020-03-21 18:41:48 +08:00
{
2020-03-25 00:03:26 +08:00
foreach ( ChatBot bot in bots . ToArray ( ) )
2020-03-26 15:01:42 +08:00
bot . OnEntityDespawn ( new Entity ( entities [ a ] . ID , entities [ a ] . TypeID , entities [ a ] . Type , entities [ a ] . Location ) ) ;
2020-03-23 19:59:00 +08:00
entities . Remove ( a ) ;
2020-03-22 20:12:06 +08:00
}
2020-03-21 18:41:48 +08:00
}
}
2020-03-21 21:44:33 +08:00
/// <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>
2020-03-21 18:41:48 +08:00
public void OnEntityPosition ( int EntityID , Double Dx , Double Dy , Double Dz , bool onGround )
{
2020-03-23 19:59:00 +08:00
if ( entities . ContainsKey ( EntityID ) )
2020-03-21 18:41:48 +08:00
{
2020-03-23 19:59:00 +08:00
Location L = entities [ EntityID ] . Location ;
2020-03-21 18:41:48 +08:00
L . X + = Dx ;
L . Y + = Dy ;
L . Z + = Dz ;
2020-03-23 19:59:00 +08:00
entities [ EntityID ] . Location = L ;
foreach ( ChatBot bot in bots . ToArray ( ) )
2020-03-26 15:01:42 +08:00
bot . OnEntityMove ( new Entity ( entities [ EntityID ] . ID , entities [ EntityID ] . TypeID , entities [ EntityID ] . Type , entities [ EntityID ] . Location ) ) ;
2020-03-21 18:41:48 +08:00
}
2020-03-23 19:59:00 +08: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>
public void OnEntityTeleport ( int EntityID , Double X , Double Y , Double Z , bool onGround )
{
if ( entities . ContainsKey ( EntityID ) )
2020-03-22 20:12:06 +08:00
{
2020-03-23 19:59:00 +08:00
Location location = new Location ( X , Y , Z ) ;
entities [ EntityID ] . Location = location ;
foreach ( ChatBot bot in bots . ToArray ( ) )
2020-03-26 15:01:42 +08:00
bot . OnEntityMove ( new Entity ( entities [ EntityID ] . ID , entities [ EntityID ] . TypeID , entities [ EntityID ] . Type , entities [ EntityID ] . Location ) ) ;
2020-03-22 20:12:06 +08:00
}
2020-03-21 18:41:48 +08:00
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Called when received entity properties from server.
/// </summary>
/// <param name="EntityID"></param>
/// <param name="prop"></param>
2020-03-21 18:41:48 +08:00
public void OnEntityProperties ( int EntityID , Dictionary < string , Double > prop )
{
if ( EntityID = = playerEntityID )
{
2020-03-23 19:59:00 +08:00
foreach ( ChatBot bot in bots . ToArray ( ) )
bot . OnPlayerProperty ( prop ) ;
2020-03-21 18:41:48 +08:00
}
}
2020-03-22 16:04:22 +08:00
2020-03-21 21:44:33 +08:00
/// <summary>
/// Called when server sent a Time Update packet.
/// </summary>
/// <param name="WorldAge"></param>
/// <param name="TimeOfDay"></param>
2020-03-21 18:41:48 +08:00
public void OnTimeUpdate ( long WorldAge , long TimeOfDay )
{
2020-03-23 19:59:00 +08:00
// calculate server tps
2020-03-21 18:41:48 +08:00
if ( lastAge ! = 0 )
{
DateTime currentTime = DateTime . Now ;
2020-03-23 19:59:00 +08:00
long tickDiff = WorldAge - lastAge ;
Double tps = tickDiff / ( currentTime - lastTime ) . TotalSeconds ;
2020-03-21 18:41:48 +08:00
lastAge = WorldAge ;
lastTime = currentTime ;
2020-03-23 19:59:00 +08:00
if ( tps < = 20.0 & & tps > = 0.0 & & serverTPS ! = tps )
2020-03-21 18:41:48 +08:00
{
serverTPS = tps ;
2020-03-23 19:59:00 +08:00
// invoke ChatBot
foreach ( ChatBot bot in bots . ToArray ( ) )
bot . OnServerTpsUpdate ( tps ) ;
2020-03-21 18:41:48 +08:00
}
}
else
{
lastAge = WorldAge ;
lastTime = DateTime . Now ;
}
}
2020-03-23 19:59:00 +08:00
2020-03-21 21:44:33 +08:00
/// <summary>
2020-03-23 19:59:00 +08:00
/// Set client player's ID for later receiving player's own properties
2020-03-21 21:44:33 +08:00
/// </summary>
2020-03-28 00:48:41 +01:00
/// <param name="EntityID">Player Entity ID</param>
2020-03-23 19:59:00 +08:00
public void SetPlayerEntityID ( int EntityID )
2020-03-21 21:44:33 +08:00
{
2020-03-23 19:59:00 +08:00
playerEntityID = EntityID ;
2020-03-21 21:44:33 +08:00
}
2020-03-28 00:48:41 +01:00
/// <summary>
/// Use the item currently in the player's hand
/// </summary>
/// <returns>TRUE if the item was successfully used</returns>
2020-03-23 19:59:00 +08:00
public bool UseItemOnHand ( )
2020-03-22 20:12:06 +08:00
{
2020-03-29 18:41:26 +02:00
return handler . SendUseItem ( 0 ) ;
}
/// <summary>
/// Click a slot in the specified window
/// </summary>
/// <returns>TRUE if the slot was successfully clicked</returns>
public bool ClickWindowSlot ( int windowId , int slotId )
{
Item item = null ;
if ( inventories . ContainsKey ( windowId ) & & inventories [ windowId ] . Items . ContainsKey ( slotId ) )
item = inventories [ windowId ] . Items [ slotId ] ;
return handler . SendClickWindow ( windowId , slotId , item ) ;
}
/// <summary>
/// Close the specified inventory window
/// </summary>
/// <param name="windowId">Window ID</param>
/// <returns>TRUE if the window was successfully closed</returns>
public bool CloseInventory ( int windowId )
{
if ( windowId ! = 0 & & inventories . ContainsKey ( windowId ) )
{
inventories . Remove ( windowId ) ;
return handler . SendCloseWindow ( windowId ) ;
}
return false ;
2020-03-22 20:12:06 +08:00
}
2020-03-21 21:44:33 +08:00
/// <summary>
2020-03-23 19:59:00 +08:00
/// Interact with an entity
2020-03-21 21:44:33 +08:00
/// </summary>
/// <param name="EntityID"></param>
2020-03-23 19:59:00 +08:00
/// <param name="type">0: interact, 1: attack, 2: interact at</param>
2020-03-29 18:41:26 +02:00
/// <returns>TRUE if interaction succeeded</returns>
2020-03-23 19:59:00 +08:00
public bool InteractEntity ( int EntityID , int type )
2020-03-22 20:12:06 +08:00
{
2020-03-29 18:41:26 +02:00
return handler . SendInteractEntity ( EntityID , type ) ;
2020-03-22 20:12:06 +08:00
}
2020-03-28 00:48:41 +01:00
/// <summary>
/// Place the block at hand in the Minecraft world
/// </summary>
/// <param name="location">Location to place block to</param>
/// <returns>TRUE if successfully placed</returns>
2020-03-26 15:01:42 +08:00
public bool PlaceBlock ( Location location )
{
2020-03-28 00:48:41 +01:00
//WORK IN PROGRESS. MAY NOT WORK YET
2020-03-26 15:01:42 +08:00
ConsoleIO . WriteLine ( location . ToString ( ) ) ;
return handler . SendPlayerBlockPlacement ( 0 , location , 1 , 0.5f , 0.5f , 0.5f , false ) ;
}
2020-03-28 00:48:41 +01:00
/// <summary>
/// Change active slot in the player inventory
/// </summary>
/// <param name="slot">Slot to activate (0 to 8)</param>
2020-03-29 18:41:26 +02:00
/// <returns>TRUE if the slot was changed</returns>
2020-03-26 15:01:42 +08:00
public bool ChangeSlot ( short slot )
{
if ( slot > = 0 & & slot < = 8 )
{
return handler . SendHeldItemChange ( slot ) ;
}
else
{
return false ;
}
}
2020-04-01 18:28:00 +08:00
/// <summary>
/// Called when client player's health changed, e.g. getting attack
/// </summary>
/// <param name="health">Player current health</param>
public void OnUpdateHealth ( float health )
{
if ( Settings . AutoRespawn )
{
if ( health < = 0 )
{
ConsoleIO . WriteLine ( "Client player dead." ) ;
ConsoleIO . WriteLine ( "Respawn after 1 second..." ) ;
Task . Factory . StartNew ( delegate
{
// wait before respawn
Thread . Sleep ( 1000 ) ;
SendRespawnPacket ( ) ;
} ) ;
}
}
}
2014-11-11 00:55:42 +11:00
}
2013-07-18 09:27:19 +02:00
}