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 ;
2020-03-22 20:12:06 +08:00
using System.Threading.Tasks ;
2013-07-18 09:27:19 +02:00
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 ;
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 > ( ) ;
2019-05-26 10:36:46 -04:00
private static List < Inventory > inventories = new List < Inventory > ( ) ;
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 ;
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-05-26 10:36:46 -04:00
private Inventory playerInventory ;
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
private Dictionary < int , Item > playerItems ;
2020-03-22 16:04:22 +08:00
// auto attack
private class Entity
{
public int ID ;
public int Type ;
public string Name ;
public Location Location ;
public Entity ( int ID , Location location )
{
this . ID = ID ;
this . Location = location ;
}
public Entity ( int ID , int Type , string Name , Location location )
{
this . ID = ID ;
this . Type = Type ;
this . Name = Name ;
this . Location = location ;
}
}
private Dictionary < int , Entity > entitiesToAttack = new Dictionary < int , Entity > ( ) ; // mobs within attack range
private Dictionary < int , Entity > entitiesToTrack = new Dictionary < int , Entity > ( ) ; // all mobs in view distance
private int attackCooldown = 6 ;
private int attackCooldownCounter = 6 ;
private Double attackSpeed ;
private Double attackCooldownSecond ;
private int attackRange = 4 ;
// server TPS
private long lastAge = 0 ;
private DateTime lastTime ;
private Double serverTPS = 0 ;
public bool AutoAttack
{
get = > Settings . AutoAttackMobs ;
set = > Settings . AutoAttackMobs = value ;
}
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 ; }
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 ;
2019-04-28 21:32:03 +02:00
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 ) ) ; }
//Add your ChatBot here by uncommenting and adapting
//BotLoad(new ChatBots.YourBot());
2020-03-21 18:41:48 +08:00
//BotLoad(new ChatBots.kill());
2017-03-29 21:25:14 +02:00
}
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>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
2015-06-20 22:58:18 +02:00
public bool PerformInternalCommand ( string command , ref string response_msg )
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 ) )
{
response_msg = cmds [ command_name ] . Run ( this , command ) ;
}
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 ( ) ;
playerInventory = null ;
}
return 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>
/// <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>
2019-09-15 17:01:53 +02:00
public void OnInventoryOpen ( Inventory inventory )
2019-05-25 17:45:59 -04:00
{
//TODO: Handle Inventory
2019-05-26 10:36:46 -04:00
if ( ! inventories . Contains ( inventory ) )
{
inventories . Add ( inventory ) ;
}
}
/// <summary>
/// When an inventory is close
/// </summary>
/// <param name="inventoryID">Location to reach</param>
2019-09-15 17:01:53 +02:00
public void OnInventoryClose ( byte inventoryID )
2019-05-26 10:36:46 -04:00
{
for ( int i = 0 ; i < inventories . Count ; i + + )
{
Inventory inventory = inventories [ i ] ;
if ( inventory = = null ) continue ;
if ( inventory . id = = inventoryID )
{
inventories . Remove ( inventory ) ;
return ;
}
}
2019-05-25 17:45:59 -04:00
}
2020-03-22 23:19:31 +08:00
/// <summary>
/// When received window items from server.
/// </summary>
/// <param name="type"></param>
/// <param name="itemList"></param>
public void OnWindowItems ( int type , Dictionary < int , Item > itemList )
{
// player inventory
//if (type == 0)
// playerItems = itemList;
foreach ( KeyValuePair < int , Item > pair in itemList )
{
ConsoleIO . WriteLine ( "Slot: " + pair . Key + " itemID:" + pair . Value . id ) ;
}
ConsoleIO . WriteLine ( "Type: " + type ) ;
}
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
}
}
2020-03-21 18:41:48 +08:00
// auto attack entity within range
2020-03-21 21:44:33 +08:00
if ( Settings . AutoAttackMobs )
2020-03-21 18:41:48 +08:00
{
2020-03-21 21:44:33 +08:00
if ( attackCooldownCounter = = 0 )
2020-03-21 18:41:48 +08:00
{
2020-03-21 21:44:33 +08:00
attackCooldownCounter = attackCooldown ;
if ( entitiesToAttack . Count > 0 )
2020-03-21 18:41:48 +08:00
{
2020-03-21 21:44:33 +08:00
foreach ( KeyValuePair < int , Entity > a in entitiesToAttack )
{
handler . SendInteractEntityPacket ( a . Key , 1 ) ;
}
2020-03-21 18:41:48 +08:00
}
}
2020-03-21 21:44:33 +08:00
else
{
attackCooldownCounter - - ;
}
2020-03-21 18:41:48 +08: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>
/// <returns>
/// dictionary of online player whereby
/// UUID represents the key
/// playername represents the value</returns>
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-21 18:41:48 +08:00
2020-03-22 20:12:06 +08:00
private Dictionary < int , Entity > fishingRod = new Dictionary < int , Entity > ( ) ;
private Double fishingHookThreshold = - 0.3 ; // must be negetive
public bool AutoFishing { get ; set ; } = false ;
public void OnSpawnEntity ( int EntityID , int EntityType , Guid UUID , Location location )
{
if ( EntityType = = 102 & & AutoFishing )
{
ConsoleIO . WriteLine ( "Threw a fishing rod" ) ;
fishingRod . Add ( EntityID , new Entity ( EntityID , EntityType , "fishing bobber" , location ) ) ;
}
}
public void OnEntityStatus ( int EntityID , byte EntityStatus )
{
if ( fishingRod . ContainsKey ( EntityID ) )
{
if ( EntityStatus = = 31 )
{
ConsoleIO . WriteLine ( "Status is bobber" ) ;
}
else
{
ConsoleIO . WriteLine ( "Status is " + EntityStatus ) ;
}
}
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Called when an Entity was created/spawned.
/// </summary>
/// <param name="EntityID"></param>
/// <param name="EntityType"></param>
/// <param name="UUID"></param>
/// <param name="location"></param>
2020-03-21 18:41:48 +08:00
public void OnSpawnLivingEntity ( int EntityID , int EntityType , Guid UUID , Location location )
{
string name = getEntityName ( EntityType ) ;
2020-03-21 21:44:33 +08:00
if ( name ! = "" )
2020-03-21 18:41:48 +08:00
{
2020-03-21 21:44:33 +08:00
Entity entity = new Entity ( EntityID , EntityType , name , location ) ;
entitiesToTrack . Add ( EntityID , entity ) ;
2020-03-22 20:12:06 +08:00
if ( Settings . AutoAttackMobs )
2020-03-21 18:41:48 +08:00
{
2020-03-22 20:12:06 +08:00
if ( calculateDistance ( location , GetCurrentLocation ( ) ) < attackRange )
{
entitiesToAttack . Add ( EntityID , 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
{
if ( entitiesToTrack . ContainsKey ( a ) )
{
entitiesToAttack . Remove ( a ) ;
entitiesToTrack . Remove ( a ) ;
}
2020-03-22 20:12:06 +08:00
if ( fishingRod . ContainsKey ( a ) )
{
fishingRod . Remove ( a ) ;
}
2020-03-21 18:41:48 +08:00
}
}
public void OnSetCooldown ( int itemID , int tick )
{
ConsoleIO . WriteLine ( "Set Cooldown on item " + itemID + " by " + tick + " ticks" ) ;
}
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 )
{
if ( entitiesToTrack . ContainsKey ( EntityID ) )
{
2020-03-21 21:44:33 +08:00
Entity entity = entitiesToTrack [ EntityID ] ;
Location L = entity . Location ;
2020-03-21 18:41:48 +08:00
L . X + = Dx ;
L . Y + = Dy ;
L . Z + = Dz ;
2020-03-21 21:44:33 +08:00
entitiesToTrack [ EntityID ] . Location = L ;
if ( entitiesToAttack . ContainsKey ( EntityID ) )
entitiesToAttack [ EntityID ] . Location = L ;
2020-03-21 18:41:48 +08:00
Double distance = calculateDistance ( L , GetCurrentLocation ( ) ) ;
2020-03-21 21:44:33 +08:00
if ( distance < attackRange )
2020-03-21 18:41:48 +08:00
{
if ( ! entitiesToAttack . ContainsKey ( EntityID ) )
{
2020-03-21 21:44:33 +08:00
entitiesToAttack . Add ( EntityID , entity ) ;
2020-03-21 18:41:48 +08:00
}
}
else
{
entitiesToAttack . Remove ( EntityID ) ;
}
}
2020-03-22 20:12:06 +08:00
if ( fishingRod . ContainsKey ( EntityID ) )
{
Location L = fishingRod [ EntityID ] . Location ;
L . X + = Dx ;
L . Y + = Dy ;
L . Z + = Dz ;
fishingRod [ EntityID ] . Location = L ;
// check if fishing hook is stationary
if ( Dx = = 0 & & Dz = = 0 )
{
if ( Dy < fishingHookThreshold )
{
// caught
OnCaughtFish ( ) ;
}
}
}
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-21 21:44:33 +08:00
// adjust auto attack cooldown for maximum attack damage
2020-03-21 18:41:48 +08:00
if ( prop . ContainsKey ( "generic.attackSpeed" ) )
{
if ( attackSpeed ! = prop [ "generic.attackSpeed" ] )
{
attackSpeed = prop [ "generic.attackSpeed" ] ;
2020-03-21 21:44:33 +08:00
attackCooldownSecond = 1 / attackSpeed * ( serverTPS / 20.0 ) ; // server tps will affect the cooldown
2020-03-21 18:41:48 +08:00
attackCooldown = Convert . ToInt16 ( Math . Truncate ( attackCooldownSecond / 0.1 ) + 1 ) ;
}
}
}
}
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-21 21:44:33 +08:00
if ( ! Settings . AutoAttackMobs ) return ;
// calculate server tps for adjusting attack cooldown
2020-03-21 18:41:48 +08:00
if ( lastAge ! = 0 )
{
DateTime currentTime = DateTime . Now ;
Double tps = ( WorldAge - lastAge ) / ( currentTime - lastTime ) . TotalSeconds ;
lastAge = WorldAge ;
lastTime = currentTime ;
if ( tps < = 20 | | tps > = 0 )
{
serverTPS = tps ;
}
}
else
{
lastAge = WorldAge ;
lastTime = DateTime . Now ;
}
}
2020-03-21 21:44:33 +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 )
{
2020-03-22 20:12:06 +08:00
if ( Settings . AutoAttackMobs )
{
if ( entitiesToTrack . ContainsKey ( EntityID ) )
{
entitiesToTrack [ EntityID ] . Location = new Location ( X , Y , Z ) ;
}
}
if ( fishingRod . ContainsKey ( EntityID ) )
2020-03-21 21:44:33 +08:00
{
2020-03-22 20:12:06 +08:00
Location L = fishingRod [ EntityID ] . Location ;
Double Dy = L . Y - Y ;
L . X = X ;
L . Y = Y ;
L . Z = Z ;
fishingRod [ EntityID ] . Location = L ;
if ( Dy < fishingHookThreshold )
{
// caught
OnCaughtFish ( ) ;
}
2020-03-21 21:44:33 +08:00
}
}
2020-03-22 20:12:06 +08:00
/// <summary>
/// Called when detected a fish is caught
/// </summary>
public void OnCaughtFish ( )
{
ConsoleIO . WriteLine ( "Caught a fish!" ) ;
// retract fishing rod
useItemOnHand ( ) ;
// retract fishing rod need some time
Task . Factory . StartNew ( delegate
{
Thread . Sleep ( 500 ) ;
// throw again
// TODO: to check if hand have fishing rod
if ( AutoFishing )
useItemOnHand ( ) ;
} ) ;
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Calculate the distance between two coordinate
/// </summary>
/// <param name="l1"></param>
/// <param name="l2"></param>
/// <returns></returns>
2020-03-21 18:41:48 +08:00
public double calculateDistance ( Location l1 , Location l2 )
{
return Math . Sqrt ( Math . Pow ( l2 . X - l1 . X , 2 ) + Math . Pow ( l2 . Y - l1 . Y , 2 ) + Math . Pow ( l2 . Z - l1 . Z , 2 ) ) ;
}
2020-03-21 21:44:33 +08:00
/// <summary>
/// Get the entity name by entity type ID.
/// </summary>
/// <param name="EntityType"></param>
/// <returns></returns>
2020-03-21 18:41:48 +08:00
public string getEntityName ( int EntityType )
{
2020-03-21 21:44:33 +08:00
// only mobs in this list will be auto attacked
2020-03-21 18:41:48 +08:00
switch ( EntityType )
{
2020-03-21 21:44:33 +08:00
case 5 : return "Blaze" ;
case 12 : return "Creeper" ;
case 16 : return "Drowned" ;
case 23 : return "Evoker" ;
case 29 : return "Ghast" ;
2020-03-21 18:41:48 +08:00
case 31 : return "Guardian" ;
2020-03-21 21:44:33 +08:00
case 33 : return "Husk" ;
case 41 : return "Magma Cube" ;
case 57 : return "Zombie Pigman" ;
case 63 : return "Shulker" ;
case 65 : return "Silverfish" ;
case 66 : return "Skeleton" ;
case 68 : return "Slime" ;
case 75 : return "Stray" ;
case 84 : return "Vex" ;
case 87 : return "Vindicator" ;
case 88 : return "Pillager" ;
case 90 : return "Witch" ;
case 92 : return "Wither Skeleton" ;
case 95 : return "Zombie" ;
case 97 : return "Zombie Villager" ;
case 98 : return "Phantom" ;
case 99 : return "Ravager" ;
2020-03-21 18:41:48 +08:00
default : return "" ;
}
}
2020-03-22 16:04:22 +08:00
2020-03-21 21:44:33 +08:00
/// <summary>
/// Set client player's ID for later receiving player's own properties
/// </summary>
/// <param name="EntityID"></param>
2020-03-21 18:41:48 +08:00
public void SetPlayerEntityID ( int EntityID )
{
playerEntityID = EntityID ;
}
2020-03-22 20:12:06 +08:00
public void useItemOnHand ( )
{
handler . SendUseItemPacket ( 0 ) ;
}
2014-11-11 00:55:42 +11:00
}
2013-07-18 09:27:19 +02:00
}