diff --git a/MinecraftClient/CSharpRunner.cs b/MinecraftClient/CSharpRunner.cs
index 64f77ca1..3e7ce558 100644
--- a/MinecraftClient/CSharpRunner.cs
+++ b/MinecraftClient/CSharpRunner.cs
@@ -22,14 +22,13 @@ namespace MinecraftClient
/// Run the specified C# script file
///
/// ChatBot handler for accessing ChatBot API
- /// Tick handler for waiting after some API calls
/// Lines of the script file to run
/// Arguments to pass to the script
/// Local variables passed along with the script
/// Set to false to compile and cache the script without launching it
/// Thrown if an error occured
/// Result of the execution, returned by the script
- public static object Run(ChatBot apiHandler, ManualResetEvent tickHandler, string[] lines, string[] args, Dictionary localVars, bool run = true)
+ public static object Run(ChatBot apiHandler, string[] lines, string[] args, Dictionary localVars, bool run = true)
{
//Script compatibility check for handling future versions differently
if (lines.Length < 1 || lines[0] != "//MCCScript 1.0")
@@ -140,7 +139,7 @@ namespace MinecraftClient
.GetType()
.GetMethod("__run")
.Invoke(compiledScript,
- new object[] { new CSharpAPI(apiHandler, tickHandler, localVars), args });
+ new object[] { new CSharpAPI(apiHandler, localVars), args });
}
catch (Exception e) { throw new CSharpException(CSErrorType.RuntimeError, e); }
}
@@ -195,11 +194,6 @@ namespace MinecraftClient
///
public class CSharpAPI : ChatBot
{
- ///
- /// Thread blocking utility for stopping execution when making a ChatBot API call
- ///
- private ManualResetEvent tickHandler;
-
///
/// Holds local variables passed along with the script
///
@@ -211,10 +205,9 @@ namespace MinecraftClient
/// ChatBot API Handler
/// ChatBot tick handler
/// Local variables passed along with the script
- public CSharpAPI(ChatBot apiHandler, ManualResetEvent tickHandler, Dictionary localVars)
+ public CSharpAPI(ChatBot apiHandler, Dictionary localVars)
{
SetMaster(apiHandler);
- this.tickHandler = tickHandler;
this.localVars = localVars;
}
@@ -237,7 +230,6 @@ namespace MinecraftClient
public bool SendText(object text)
{
base.SendText(text is string ? (string)text : text.ToString());
- tickHandler.WaitOne();
return true;
}
@@ -252,7 +244,6 @@ namespace MinecraftClient
if (localVars == null)
localVars = this.localVars;
bool result = base.PerformInternalCommand(command, localVars);
- tickHandler.WaitOne();
return result;
}
@@ -267,7 +258,6 @@ namespace MinecraftClient
if (extraAttempts == -999999)
base.ReconnectToTheServer();
else base.ReconnectToTheServer(extraAttempts);
- tickHandler.WaitOne();
}
///
@@ -276,7 +266,6 @@ namespace MinecraftClient
new public void DisconnectAndExit()
{
base.DisconnectAndExit();
- tickHandler.WaitOne();
}
///
@@ -286,7 +275,6 @@ namespace MinecraftClient
new public void LoadBot(ChatBot bot)
{
base.LoadBot(bot);
- tickHandler.WaitOne();
}
///
@@ -406,7 +394,7 @@ namespace MinecraftClient
ChatBots.Script.LookForScript(ref script);
try { lines = File.ReadAllLines(script, Encoding.UTF8); }
catch (Exception e) { throw new CSharpException(CSErrorType.FileReadError, e); }
- return CSharpRunner.Run(this, tickHandler, lines, args, localVars);
+ return CSharpRunner.Run(this, lines, args, localVars);
}
}
}
diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs
index e15bd771..59c10a76 100644
--- a/MinecraftClient/ChatBot.cs
+++ b/MinecraftClient/ChatBot.cs
@@ -42,7 +42,7 @@ namespace MinecraftClient
private List registeredPluginChannels = new List();
private List registeredCommands = new List();
private object delayTasksLock = new object();
- private List delayTasks = new List();
+ private List delayedTasks = new List();
private McClient Handler
{
get
@@ -65,14 +65,14 @@ namespace MinecraftClient
{
lock (delayTasksLock)
{
- if (delayTasks.Count > 0)
+ if (delayedTasks.Count > 0)
{
List tasksToRemove = new List();
- for (int i = 0; i < delayTasks.Count; i++)
+ for (int i = 0; i < delayedTasks.Count; i++)
{
- if (delayTasks[i].Tick())
+ if (delayedTasks[i].Tick())
{
- delayTasks[i].Task.DynamicInvoke();
+ delayedTasks[i].Task();
tasksToRemove.Add(i);
}
}
@@ -81,7 +81,7 @@ namespace MinecraftClient
tasksToRemove.Sort((a, b) => b.CompareTo(a)); // descending sort
foreach (int index in tasksToRemove)
{
- delayTasks.RemoveAt(index);
+ delayedTasks.RemoveAt(index);
}
}
}
@@ -1390,46 +1390,61 @@ namespace MinecraftClient
}
///
- /// Schedule a task to run on main thread. Returned value cannot be retrieved
+ /// Invoke a task on the main thread, wait for completion and retrieve return value.
+ ///
+ /// Task to run with any type or return value
+ /// Any result returned from task, result type is inferred from the task
+ /// bool result = InvokeOnMainThread(methodThatReturnsAbool);
+ /// bool result = InvokeOnMainThread(() => methodThatReturnsAbool(argument));
+ /// int result = InvokeOnMainThread(() => { yourCode(); return 42; });
+ /// Type of the return value
+ protected T InvokeOnMainThread(Func task)
+ {
+ return Handler.InvokeOnMainThread(task);
+ }
+
+ ///
+ /// Invoke a task on the main thread and wait for completion
+ ///
+ /// Task to run without return value
+ /// InvokeOnMainThread(methodThatReturnsNothing);
+ /// InvokeOnMainThread(() => methodThatReturnsNothing(argument));
+ /// InvokeOnMainThread(() => { yourCode(); });
+ protected void InvokeOnMainThread(Action task)
+ {
+ Handler.InvokeOnMainThread(task);
+ }
+
+ ///
+ /// Schedule a task to run on the main thread, and do not wait for completion
///
/// Task to run
/// Run the task after X ticks (1 tick delay = ~100ms). 0 for no delay
///
- /// // Delay ~10 seconds
- /// ScheduleTaskDelayed(new Action(() =>
- /// {
- /// /** Your code here **/
- /// Console.WriteLine("10 seconds has passed");
- /// }), 100);
+ /// InvokeOnMainThread(methodThatReturnsNothing, 10);
+ /// InvokeOnMainThread(() => methodThatReturnsNothing(argument), 10);
+ /// InvokeOnMainThread(() => { yourCode(); }, 10);
///
- // TODO: Adapt to new IMinecraftComHandler API
- /*protected void ScheduleTaskDelayed(Delegate task, int delayTicks = 0)
+ protected void ScheduleOnMainThread(Action task, int delayTicks = 0)
{
- if (delayTicks <= 0)
+ lock (delayTasksLock)
{
- // Immediately schedule to run on next update
- Handler.InvokeOnMainThread(task);
+ delayedTasks.Add(new TaskWithDelay(task, delayTicks));
}
- else
- {
- lock (delayTasksLock)
- {
- delayTasks.Add(new DelayedTask(task, delayTicks));
- }
- }
- }*/
+ }
///
- /// Schedule a task to run on main thread.
+ /// Schedule a task to run on the main thread, and do not wait for completion
///
/// Task to run
- /// Any value returned from the task
- // TODO: Adapt to new IMinecraftComHandler API
- /*
- protected object ScheduleTask(Delegate task)
+ /// Run the task after the specified delay
+ protected void ScheduleOnMainThread(Action task, TimeSpan delay)
{
- return Handler.InvokeOnMainThread(task);
- }*/
+ lock (delayTasksLock)
+ {
+ delayedTasks.Add(new TaskWithDelay(task, delay));
+ }
+ }
///
/// Command runner definition.
@@ -1475,35 +1490,5 @@ namespace MinecraftClient
this.Runner = callback;
}
}
-
- private class DelayedTask
- {
- private Delegate task;
- private int Counter;
-
- public Delegate Task { get { return task; } }
-
- public DelayedTask(Delegate task)
- : this(task, 0)
- { }
-
- public DelayedTask(Delegate task, int delayTicks)
- {
- this.task = task;
- Counter = delayTicks;
- }
-
- ///
- /// Tick the counter
- ///
- /// Return true if counted to zero
- public bool Tick()
- {
- Counter--;
- if (Counter <= 0)
- return true;
- return false;
- }
- }
}
}
diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs
index a86f49a1..c6c6c2c8 100644
--- a/MinecraftClient/ChatBots/Script.cs
+++ b/MinecraftClient/ChatBots/Script.cs
@@ -24,7 +24,6 @@ namespace MinecraftClient.ChatBots
private string owner;
private bool csharp;
private Thread thread;
- private ManualResetEvent tpause;
private Dictionary localVars;
public Script(string filename)
@@ -157,12 +156,11 @@ namespace MinecraftClient.ChatBots
//Initialize thread on first update
if (thread == null)
{
- tpause = new ManualResetEvent(false);
thread = new Thread(() =>
{
try
{
- CSharpRunner.Run(this, tpause, lines, args, localVars);
+ CSharpRunner.Run(this, lines, args, localVars);
}
catch (CSharpException e)
{
@@ -173,16 +171,14 @@ namespace MinecraftClient.ChatBots
LogToConsole(e.InnerException);
}
});
+ thread.Name = "MCC Script - " + file;
thread.Start();
}
- //Let the thread run for a short span of time
- if (thread != null)
+ //Unload bot once the thread has finished running
+ if (thread != null && !thread.IsAlive)
{
- tpause.Set();
- tpause.Reset();
- if (!thread.IsAlive)
- UnloadBot();
+ UnloadBot();
}
}
else //Classic MCC script interpreter
diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs
index d825c95d..6e778828 100644
--- a/MinecraftClient/McClient.cs
+++ b/MinecraftClient/McClient.cs
@@ -764,13 +764,16 @@ namespace MinecraftClient
///
public void BotLoad(ChatBot b, bool init = true)
{
- b.SetHandler(this);
- bots.Add(b);
- if (init)
- DispatchBotEvent(bot => bot.Initialize(), new ChatBot[] { b });
- if (this.handler != null)
- DispatchBotEvent(bot => bot.AfterGameJoined(), new ChatBot[] { b });
- Settings.SingleCommand = "";
+ InvokeOnMainThread(() =>
+ {
+ b.SetHandler(this);
+ bots.Add(b);
+ if (init)
+ DispatchBotEvent(bot => bot.Initialize(), new ChatBot[] { b });
+ if (this.handler != null)
+ DispatchBotEvent(bot => bot.AfterGameJoined(), new ChatBot[] { b });
+ Settings.SingleCommand = "";
+ });
}
///
@@ -778,14 +781,17 @@ namespace MinecraftClient
///
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)
+ InvokeOnMainThread(() =>
{
- UnregisterPluginChannel(entry.Key, 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);
+ }
+ });
}
///
@@ -793,7 +799,7 @@ namespace MinecraftClient
///
public void BotClear()
{
- bots.Clear();
+ InvokeOnMainThread(bots.Clear);
}
///
@@ -812,57 +818,6 @@ namespace MinecraftClient
return inventoryHandlingEnabled;
}
- ///
- /// Enable or disable Terrain and Movements.
- /// Please note that Enabling will be deferred until next relog, respawn or world change.
- ///
- /// Enabled
- /// TRUE if the setting was applied immediately, FALSE if delayed.
- public bool SetTerrainEnabled(bool enabled)
- {
- if (enabled)
- {
- if (!terrainAndMovementsEnabled)
- {
- terrainAndMovementsRequested = true;
- return false;
- }
- }
- else
- {
- terrainAndMovementsEnabled = false;
- terrainAndMovementsRequested = false;
- locationReceived = false;
- world.Clear();
- }
- return true;
- }
-
- ///
- /// Enable or disable Inventories.
- /// Please note that Enabling will be deferred until next relog.
- ///
- /// Enabled
- /// TRUE if the setting was applied immediately, FALSE if delayed.
- public bool SetInventoryEnabled(bool enabled)
- {
- if (enabled)
- {
- if (!inventoryHandlingEnabled)
- {
- inventoryHandlingRequested = true;
- return false;
- }
- }
- else
- {
- inventoryHandlingEnabled = false;
- inventoryHandlingRequested = false;
- inventories.Clear();
- }
- return true;
- }
-
///
/// Get entity handling status
///
@@ -873,6 +828,63 @@ namespace MinecraftClient
return entityHandlingEnabled;
}
+ ///
+ /// Enable or disable Terrain and Movements.
+ /// Please note that Enabling will be deferred until next relog, respawn or world change.
+ ///
+ /// Enabled
+ /// TRUE if the setting was applied immediately, FALSE if delayed.
+ public bool SetTerrainEnabled(bool enabled)
+ {
+ return InvokeOnMainThread(() =>
+ {
+ if (enabled)
+ {
+ if (!terrainAndMovementsEnabled)
+ {
+ terrainAndMovementsRequested = true;
+ return false;
+ }
+ }
+ else
+ {
+ terrainAndMovementsEnabled = false;
+ terrainAndMovementsRequested = false;
+ locationReceived = false;
+ world.Clear();
+ }
+ return true;
+ });
+ }
+
+ ///
+ /// Enable or disable Inventories.
+ /// Please note that Enabling will be deferred until next relog.
+ ///
+ /// Enabled
+ /// TRUE if the setting was applied immediately, FALSE if delayed.
+ public bool SetInventoryEnabled(bool enabled)
+ {
+ return InvokeOnMainThread(() =>
+ {
+ if (enabled)
+ {
+ if (!inventoryHandlingEnabled)
+ {
+ inventoryHandlingRequested = true;
+ return false;
+ }
+ }
+ else
+ {
+ inventoryHandlingEnabled = false;
+ inventoryHandlingRequested = false;
+ inventories.Clear();
+ }
+ return true;
+ });
+ }
+
///
/// Enable or disable Entity handling.
/// Please note that Enabling will be deferred until next relog.
@@ -881,23 +893,26 @@ namespace MinecraftClient
/// TRUE if the setting was applied immediately, FALSE if delayed.
public bool SetEntityHandlingEnabled(bool enabled)
{
- if (!enabled)
+ return InvokeOnMainThread(() =>
{
- if (entityHandlingEnabled)
+ if (!enabled)
{
- entityHandlingEnabled = false;
- return true;
+ if (entityHandlingEnabled)
+ {
+ entityHandlingEnabled = false;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
else
{
+ // Entity Handling cannot be enabled in runtime (or after joining server)
return false;
}
- }
- else
- {
- // Entity Handling cannot be enabled in runtime (or after joining server)
- return false;
- }
+ });
}
///
@@ -909,7 +924,7 @@ namespace MinecraftClient
///
public void SetNetworkPacketCaptureEnabled(bool enabled)
{
- networkPacketCaptureEnabled = enabled;
+ InvokeOnMainThread(() => { networkPacketCaptureEnabled = enabled; });
}
#endregion
@@ -959,9 +974,12 @@ namespace MinecraftClient
/// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)
public Container GetInventory(int inventoryID)
{
- if (inventories.ContainsKey(inventoryID))
- return inventories[inventoryID];
- return null;
+ return InvokeOnMainThread(() =>
+ {
+ if (inventories.ContainsKey(inventoryID))
+ return inventories[inventoryID];
+ return null;
+ });
}
///
@@ -1075,7 +1093,7 @@ namespace MinecraftClient
/// True if packet successfully sent
public bool SendRespawnPacket()
{
- return handler.SendRespawnPacket();
+ return InvokeOnMainThread(handler.SendRespawnPacket);
}
///
@@ -1085,17 +1103,20 @@ namespace MinecraftClient
/// The bot to register the channel for.
public void RegisterPluginChannel(string channel, ChatBot bot)
{
- if (registeredBotPluginChannels.ContainsKey(channel))
+ InvokeOnMainThread(() =>
{
- registeredBotPluginChannels[channel].Add(bot);
- }
- else
- {
- List bots = new List();
- bots.Add(bot);
- registeredBotPluginChannels[channel] = bots;
- SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true);
- }
+ if (registeredBotPluginChannels.ContainsKey(channel))
+ {
+ registeredBotPluginChannels[channel].Add(bot);
+ }
+ else
+ {
+ List bots = new List();
+ bots.Add(bot);
+ registeredBotPluginChannels[channel] = bots;
+ SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true);
+ }
+ });
}
///
@@ -1105,16 +1126,19 @@ namespace MinecraftClient
/// The bot to unregister the channel for.
public void UnregisterPluginChannel(string channel, ChatBot bot)
{
- if (registeredBotPluginChannels.ContainsKey(channel))
+ InvokeOnMainThread(() =>
{
- List registeredBots = registeredBotPluginChannels[channel];
- registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot));
- if (registeredBots.Count == 0)
+ if (registeredBotPluginChannels.ContainsKey(channel))
{
- registeredBotPluginChannels.Remove(channel);
- SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true);
+ List registeredBots = registeredBotPluginChannels[channel];
+ registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot));
+ if (registeredBots.Count == 0)
+ {
+ registeredBotPluginChannels.Remove(channel);
+ SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true);
+ }
}
- }
+ });
}
///
@@ -1127,18 +1151,21 @@ namespace MinecraftClient
/// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered.
public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false)
{
- if (!sendEvenIfNotRegistered)
+ return InvokeOnMainThread(() =>
{
- if (!registeredBotPluginChannels.ContainsKey(channel))
+ if (!sendEvenIfNotRegistered)
{
- return false;
+ if (!registeredBotPluginChannels.ContainsKey(channel))
+ {
+ return false;
+ }
+ if (!registeredServerPluginChannels.Contains(channel))
+ {
+ return false;
+ }
}
- if (!registeredServerPluginChannels.Contains(channel))
- {
- return false;
- }
- }
- return handler.SendPluginChannelPacket(channel, data);
+ return handler.SendPluginChannelPacket(channel, data);
+ });
}
///
@@ -1147,7 +1174,7 @@ namespace MinecraftClient
/// TRUE if the item was successfully used
public bool SendEntityAction(EntityActionType entityAction)
{
- return handler.SendEntityAction(playerEntityID, (int)entityAction);
+ return InvokeOnMainThread(() => handler.SendEntityAction(playerEntityID, (int)entityAction));
}
///
@@ -1156,7 +1183,7 @@ namespace MinecraftClient
/// TRUE if the item was successfully used
public bool UseItemOnHand()
{
- return handler.SendUseItem(0);
+ return InvokeOnMainThread(() => handler.SendUseItem(0));
}
///
@@ -1165,287 +1192,290 @@ namespace MinecraftClient
/// TRUE if the slot was successfully clicked
public bool DoWindowAction(int windowId, int slotId, WindowActionType action)
{
- Item item = null;
- if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId))
- item = inventories[windowId].Items[slotId];
-
- // Inventory update must be after sending packet
- bool result = handler.SendWindowAction(windowId, slotId, action, item);
-
- // Update our inventory base on action type
- var inventory = GetInventory(windowId);
- var playerInventory = GetInventory(0);
- if (inventory != null)
+ return InvokeOnMainThread(() =>
{
- switch (action)
- {
- case WindowActionType.LeftClick:
- // Check if cursor have item (slot -1)
- if (playerInventory.Items.ContainsKey(-1))
- {
- // When item on cursor and clicking slot 0, nothing will happen
- if (slotId == 0) break;
+ Item item = null;
+ if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId))
+ item = inventories[windowId].Items[slotId];
- // Check target slot also have item?
- if (inventory.Items.ContainsKey(slotId))
+ // Inventory update must be after sending packet
+ bool result = handler.SendWindowAction(windowId, slotId, action, item);
+
+ // Update our inventory base on action type
+ var inventory = GetInventory(windowId);
+ var playerInventory = GetInventory(0);
+ if (inventory != null)
+ {
+ switch (action)
+ {
+ case WindowActionType.LeftClick:
+ // Check if cursor have item (slot -1)
+ if (playerInventory.Items.ContainsKey(-1))
{
- // Check if both item are the same?
- if (inventory.Items[slotId].Type == playerInventory.Items[-1].Type)
+ // When item on cursor and clicking slot 0, nothing will happen
+ if (slotId == 0) break;
+
+ // Check target slot also have item?
+ if (inventory.Items.ContainsKey(slotId))
{
- int maxCount = inventory.Items[slotId].Type.StackCount();
- // Check item stacking
- if ((inventory.Items[slotId].Count + playerInventory.Items[-1].Count) <= maxCount)
+ // Check if both item are the same?
+ if (inventory.Items[slotId].Type == playerInventory.Items[-1].Type)
{
- // Put cursor item to target
- inventory.Items[slotId].Count += playerInventory.Items[-1].Count;
- playerInventory.Items.Remove(-1);
+ int maxCount = inventory.Items[slotId].Type.StackCount();
+ // Check item stacking
+ if ((inventory.Items[slotId].Count + playerInventory.Items[-1].Count) <= maxCount)
+ {
+ // Put cursor item to target
+ inventory.Items[slotId].Count += playerInventory.Items[-1].Count;
+ playerInventory.Items.Remove(-1);
+ }
+ else
+ {
+ // Leave some item on cursor
+ playerInventory.Items[-1].Count -= (maxCount - inventory.Items[slotId].Count);
+ inventory.Items[slotId].Count = maxCount;
+ }
}
else
{
- // Leave some item on cursor
- playerInventory.Items[-1].Count -= (maxCount - inventory.Items[slotId].Count);
- inventory.Items[slotId].Count = maxCount;
+ // Swap two items
+ var itemTmp = playerInventory.Items[-1];
+ playerInventory.Items[-1] = inventory.Items[slotId];
+ inventory.Items[slotId] = itemTmp;
}
}
else
{
- // Swap two items
- var itemTmp = playerInventory.Items[-1];
- playerInventory.Items[-1] = inventory.Items[slotId];
- inventory.Items[slotId] = itemTmp;
+ // Put cursor item to target
+ inventory.Items[slotId] = playerInventory.Items[-1];
+ playerInventory.Items.Remove(-1);
}
}
else
{
- // Put cursor item to target
- inventory.Items[slotId] = playerInventory.Items[-1];
- playerInventory.Items.Remove(-1);
- }
- }
- else
- {
- // Check target slot have item?
- if (inventory.Items.ContainsKey(slotId))
- {
- // When taking item from slot 0, server will update us
- if (slotId == 0) break;
+ // Check target slot have item?
+ if (inventory.Items.ContainsKey(slotId))
+ {
+ // When taking item from slot 0, server will update us
+ if (slotId == 0) break;
- // Put target slot item to cursor
- playerInventory.Items[-1] = inventory.Items[slotId];
- inventory.Items.Remove(slotId);
- }
- }
- break;
- case WindowActionType.RightClick:
- // Check if cursor have item (slot -1)
- if (playerInventory.Items.ContainsKey(-1))
- {
- // When item on cursor and clicking slot 0, nothing will happen
- if (slotId == 0) break;
-
- // Check target slot have item?
- if (inventory.Items.ContainsKey(slotId))
- {
- // Check if both item are the same?
- if (inventory.Items[slotId].Type == playerInventory.Items[-1].Type)
- {
- // Check item stacking
- if (inventory.Items[slotId].Count < inventory.Items[slotId].Type.StackCount())
- {
- // Drop 1 item count from cursor
- playerInventory.Items[-1].Count--;
- inventory.Items[slotId].Count++;
- }
- }
- else
- {
- // Swap two items
- var itemTmp = playerInventory.Items[-1];
- playerInventory.Items[-1] = inventory.Items[slotId];
- inventory.Items[slotId] = itemTmp;
- }
- }
- else
- {
- // Drop 1 item count from cursor
- var itemTmp = playerInventory.Items[-1];
- var itemClone = new Item(itemTmp.Type, 1, itemTmp.NBT);
- inventory.Items[slotId] = itemClone;
- playerInventory.Items[-1].Count--;
- }
- }
- else
- {
- // Check target slot have item?
- if (inventory.Items.ContainsKey(slotId))
- {
- if (slotId == 0)
- {
- // no matter how many item in slot 0, only 1 will be taken out
- // Also server will update us
- break;
- }
- if (inventory.Items[slotId].Count == 1)
- {
- // Only 1 item count. Put it to cursor
+ // Put target slot item to cursor
playerInventory.Items[-1] = inventory.Items[slotId];
inventory.Items.Remove(slotId);
}
- else
+ }
+ break;
+ case WindowActionType.RightClick:
+ // Check if cursor have item (slot -1)
+ if (playerInventory.Items.ContainsKey(-1))
+ {
+ // When item on cursor and clicking slot 0, nothing will happen
+ if (slotId == 0) break;
+
+ // Check target slot have item?
+ if (inventory.Items.ContainsKey(slotId))
{
- // Take half of the item stack to cursor
- if (inventory.Items[slotId].Count % 2 == 0)
+ // Check if both item are the same?
+ if (inventory.Items[slotId].Type == playerInventory.Items[-1].Type)
{
- // Can be evenly divided
- Item itemTmp = inventory.Items[slotId];
- playerInventory.Items[-1] = new Item(itemTmp.Type, itemTmp.Count / 2, itemTmp.NBT);
- inventory.Items[slotId].Count = itemTmp.Count / 2;
+ // Check item stacking
+ if (inventory.Items[slotId].Count < inventory.Items[slotId].Type.StackCount())
+ {
+ // Drop 1 item count from cursor
+ playerInventory.Items[-1].Count--;
+ inventory.Items[slotId].Count++;
+ }
}
else
{
- // Cannot be evenly divided. item count on cursor is always larger than item on inventory
- Item itemTmp = inventory.Items[slotId];
- playerInventory.Items[-1] = new Item(itemTmp.Type, (itemTmp.Count + 1) / 2, itemTmp.NBT);
- inventory.Items[slotId].Count = (itemTmp.Count - 1) / 2;
+ // Swap two items
+ var itemTmp = playerInventory.Items[-1];
+ playerInventory.Items[-1] = inventory.Items[slotId];
+ inventory.Items[slotId] = itemTmp;
}
}
- }
- }
- break;
- case WindowActionType.ShiftClick:
- if (slotId == 0) break;
- if (inventory.Items.ContainsKey(slotId))
- {
- /* Target slot have item */
-
- int upperStartSlot = 9;
- int upperEndSlot = 35;
-
- switch (inventory.Type)
- {
- case ContainerType.PlayerInventory:
- upperStartSlot = 9;
- upperEndSlot = 35;
- break;
- case ContainerType.Crafting:
- upperStartSlot = 1;
- upperEndSlot = 9;
- break;
- // TODO: Define more container type here
- }
-
- // Cursor have item or not doesn't matter
- // If hotbar already have same item, will put on it first until every stack are full
- // If no more same item , will put on the first empty slot (smaller slot id)
- // If inventory full, item will not move
- if (slotId <= upperEndSlot)
- {
- // Clicked slot is on upper side inventory, put it to hotbar
- // Now try to find same item and put on them
- var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value);
- foreach (KeyValuePair _item in itemsClone)
+ else
{
- if (_item.Key <= upperEndSlot) continue;
-
- int maxCount = _item.Value.Type.StackCount();
- if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount)
- {
- // Put item on that stack
- int spaceLeft = maxCount - _item.Value.Count;
- if (inventory.Items[slotId].Count <= spaceLeft)
- {
- // Can fit into the stack
- inventory.Items[_item.Key].Count += inventory.Items[slotId].Count;
- inventory.Items.Remove(slotId);
- }
- else
- {
- inventory.Items[slotId].Count -= spaceLeft;
- inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount();
- }
- }
- }
- if (inventory.Items[slotId].Count > 0)
- {
- int[] emptySlots = inventory.GetEmpytSlots();
- int emptySlot = -2;
- foreach (int slot in emptySlots)
- {
- if (slot <= upperEndSlot) continue;
- emptySlot = slot;
- break;
- }
- if (emptySlot != -2)
- {
- var itemTmp = inventory.Items[slotId];
- inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT);
- inventory.Items.Remove(slotId);
- }
+ // Drop 1 item count from cursor
+ var itemTmp = playerInventory.Items[-1];
+ var itemClone = new Item(itemTmp.Type, 1, itemTmp.NBT);
+ inventory.Items[slotId] = itemClone;
+ playerInventory.Items[-1].Count--;
}
}
else
{
- // Clicked slot is on hotbar, put it to upper inventory
- // Now try to find same item and put on them
- var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value);
- foreach (KeyValuePair _item in itemsClone)
+ // Check target slot have item?
+ if (inventory.Items.ContainsKey(slotId))
{
- if (_item.Key < upperStartSlot) continue;
- if (_item.Key >= upperEndSlot) break;
-
- int maxCount = _item.Value.Type.StackCount();
- if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount)
+ if (slotId == 0)
{
- // Put item on that stack
- int spaceLeft = maxCount - _item.Value.Count;
- if (inventory.Items[slotId].Count <= spaceLeft)
+ // no matter how many item in slot 0, only 1 will be taken out
+ // Also server will update us
+ break;
+ }
+ if (inventory.Items[slotId].Count == 1)
+ {
+ // Only 1 item count. Put it to cursor
+ playerInventory.Items[-1] = inventory.Items[slotId];
+ inventory.Items.Remove(slotId);
+ }
+ else
+ {
+ // Take half of the item stack to cursor
+ if (inventory.Items[slotId].Count % 2 == 0)
{
- // Can fit into the stack
- inventory.Items[_item.Key].Count += inventory.Items[slotId].Count;
- inventory.Items.Remove(slotId);
+ // Can be evenly divided
+ Item itemTmp = inventory.Items[slotId];
+ playerInventory.Items[-1] = new Item(itemTmp.Type, itemTmp.Count / 2, itemTmp.NBT);
+ inventory.Items[slotId].Count = itemTmp.Count / 2;
}
else
{
- inventory.Items[slotId].Count -= spaceLeft;
- inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount();
+ // Cannot be evenly divided. item count on cursor is always larger than item on inventory
+ Item itemTmp = inventory.Items[slotId];
+ playerInventory.Items[-1] = new Item(itemTmp.Type, (itemTmp.Count + 1) / 2, itemTmp.NBT);
+ inventory.Items[slotId].Count = (itemTmp.Count - 1) / 2;
}
}
}
- if (inventory.Items[slotId].Count > 0)
+ }
+ break;
+ case WindowActionType.ShiftClick:
+ if (slotId == 0) break;
+ if (inventory.Items.ContainsKey(slotId))
+ {
+ /* Target slot have item */
+
+ int upperStartSlot = 9;
+ int upperEndSlot = 35;
+
+ switch (inventory.Type)
{
- int[] emptySlots = inventory.GetEmpytSlots();
- int emptySlot = -2;
- foreach (int slot in emptySlots)
- {
- if (slot < upperStartSlot) continue;
- if (slot >= upperEndSlot) break;
- emptySlot = slot;
+ case ContainerType.PlayerInventory:
+ upperStartSlot = 9;
+ upperEndSlot = 35;
break;
- }
- if (emptySlot != -2)
+ case ContainerType.Crafting:
+ upperStartSlot = 1;
+ upperEndSlot = 9;
+ break;
+ // TODO: Define more container type here
+ }
+
+ // Cursor have item or not doesn't matter
+ // If hotbar already have same item, will put on it first until every stack are full
+ // If no more same item , will put on the first empty slot (smaller slot id)
+ // If inventory full, item will not move
+ if (slotId <= upperEndSlot)
+ {
+ // Clicked slot is on upper side inventory, put it to hotbar
+ // Now try to find same item and put on them
+ var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value);
+ foreach (KeyValuePair _item in itemsClone)
{
- var itemTmp = inventory.Items[slotId];
- inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT);
- inventory.Items.Remove(slotId);
+ if (_item.Key <= upperEndSlot) continue;
+
+ int maxCount = _item.Value.Type.StackCount();
+ if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount)
+ {
+ // Put item on that stack
+ int spaceLeft = maxCount - _item.Value.Count;
+ if (inventory.Items[slotId].Count <= spaceLeft)
+ {
+ // Can fit into the stack
+ inventory.Items[_item.Key].Count += inventory.Items[slotId].Count;
+ inventory.Items.Remove(slotId);
+ }
+ else
+ {
+ inventory.Items[slotId].Count -= spaceLeft;
+ inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount();
+ }
+ }
+ }
+ if (inventory.Items[slotId].Count > 0)
+ {
+ int[] emptySlots = inventory.GetEmpytSlots();
+ int emptySlot = -2;
+ foreach (int slot in emptySlots)
+ {
+ if (slot <= upperEndSlot) continue;
+ emptySlot = slot;
+ break;
+ }
+ if (emptySlot != -2)
+ {
+ var itemTmp = inventory.Items[slotId];
+ inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT);
+ inventory.Items.Remove(slotId);
+ }
+ }
+ }
+ else
+ {
+ // Clicked slot is on hotbar, put it to upper inventory
+ // Now try to find same item and put on them
+ var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value);
+ foreach (KeyValuePair _item in itemsClone)
+ {
+ if (_item.Key < upperStartSlot) continue;
+ if (_item.Key >= upperEndSlot) break;
+
+ int maxCount = _item.Value.Type.StackCount();
+ if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount)
+ {
+ // Put item on that stack
+ int spaceLeft = maxCount - _item.Value.Count;
+ if (inventory.Items[slotId].Count <= spaceLeft)
+ {
+ // Can fit into the stack
+ inventory.Items[_item.Key].Count += inventory.Items[slotId].Count;
+ inventory.Items.Remove(slotId);
+ }
+ else
+ {
+ inventory.Items[slotId].Count -= spaceLeft;
+ inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount();
+ }
+ }
+ }
+ if (inventory.Items[slotId].Count > 0)
+ {
+ int[] emptySlots = inventory.GetEmpytSlots();
+ int emptySlot = -2;
+ foreach (int slot in emptySlots)
+ {
+ if (slot < upperStartSlot) continue;
+ if (slot >= upperEndSlot) break;
+ emptySlot = slot;
+ break;
+ }
+ if (emptySlot != -2)
+ {
+ var itemTmp = inventory.Items[slotId];
+ inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT);
+ inventory.Items.Remove(slotId);
+ }
}
}
}
- }
- break;
- case WindowActionType.DropItem:
- if (inventory.Items.ContainsKey(slotId))
- inventory.Items[slotId].Count--;
-
- if (inventory.Items[slotId].Count <= 0)
- inventory.Items.Remove(slotId);
- break;
- case WindowActionType.DropItemStack:
- inventory.Items.Remove(slotId);
- break;
- }
- }
+ break;
+ case WindowActionType.DropItem:
+ if (inventory.Items.ContainsKey(slotId))
+ inventory.Items[slotId].Count--;
- return result;
+ if (inventory.Items[slotId].Count <= 0)
+ inventory.Items.Remove(slotId);
+ break;
+ case WindowActionType.DropItemStack:
+ inventory.Items.Remove(slotId);
+ break;
+ }
+ }
+
+ return result;
+ });
}
///
@@ -1459,7 +1489,7 @@ namespace MinecraftClient
/// TRUE if item given successfully
public bool DoCreativeGive(int slot, ItemType itemType, int count, Dictionary nbt = null)
{
- return handler.SendCreativeInventoryAction(slot, itemType, count, nbt);
+ return InvokeOnMainThread(() => handler.SendCreativeInventoryAction(slot, itemType, count, nbt));
}
///
@@ -1469,7 +1499,7 @@ namespace MinecraftClient
/// TRUE if animation successfully done
public bool DoAnimation(int animation)
{
- return handler.SendAnimation(animation, playerEntityID);
+ return InvokeOnMainThread(() => handler.SendAnimation(animation, playerEntityID));
}
///
@@ -1480,13 +1510,16 @@ namespace MinecraftClient
/// Sending close window for inventory 0 can cause server to update our inventory if there are any item in the crafting area
public bool CloseInventory(int windowId)
{
- if (inventories.ContainsKey(windowId))
+ return InvokeOnMainThread(() =>
{
- if (windowId != 0)
- inventories.Remove(windowId);
- return handler.SendCloseWindow(windowId);
- }
- return false;
+ if (inventories.ContainsKey(windowId))
+ {
+ if (windowId != 0)
+ inventories.Remove(windowId);
+ return handler.SendCloseWindow(windowId);
+ }
+ return false;
+ });
}
///
@@ -1495,13 +1528,14 @@ namespace MinecraftClient
/// TRUE if the uccessfully clear
public bool ClearInventories()
{
- if (inventoryHandlingEnabled)
+ if (!inventoryHandlingEnabled)
+ return false;
+ return InvokeOnMainThread(() =>
{
inventories.Clear();
inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory");
return true;
- }
- else { return false; }
+ });
}
///
@@ -1513,18 +1547,21 @@ namespace MinecraftClient
/// TRUE if interaction succeeded
public bool InteractEntity(int EntityID, int type, Hand hand = Hand.MainHand)
{
- if (entities.ContainsKey(EntityID))
+ return InvokeOnMainThread(() =>
{
- if (type == 0)
+ if (entities.ContainsKey(EntityID))
{
- return handler.SendInteractEntity(EntityID, type, (int)hand);
+ if (type == 0)
+ {
+ return handler.SendInteractEntity(EntityID, type, (int)hand);
+ }
+ else
+ {
+ return handler.SendInteractEntity(EntityID, type);
+ }
}
- else
- {
- return handler.SendInteractEntity(EntityID, type);
- }
- }
- else { return false; }
+ else return false;
+ });
}
///
@@ -1535,7 +1572,7 @@ namespace MinecraftClient
/// TRUE if successfully placed
public bool PlaceBlock(Location location, Direction blockFace, Hand hand = Hand.MainHand)
{
- return handler.SendPlayerBlockPlacement((int)hand, location, blockFace);
+ return InvokeOnMainThread(() => handler.SendPlayerBlockPlacement((int)hand, location, blockFace));
}
///
@@ -1546,7 +1583,9 @@ namespace MinecraftClient
/// Also look at the block before digging
public bool DigBlock(Location location, bool swingArms = true, bool lookAtBlock = true)
{
- if (GetTerrainEnabled())
+ if (!GetTerrainEnabled())
+ return false;
+ return InvokeOnMainThread(() =>
{
// TODO select best face from current player location
Direction blockFace = Direction.Down;
@@ -1560,8 +1599,7 @@ namespace MinecraftClient
return handler.SendPlayerDigging(0, location, blockFace)
&& (!swingArms || DoAnimation((int)Hand.MainHand))
&& handler.SendPlayerDigging(2, location, blockFace);
- }
- else return false;
+ });
}
///
@@ -1573,8 +1611,11 @@ namespace MinecraftClient
{
if (slot >= 0 && slot <= 8)
{
- CurrentSlot = Convert.ToByte(slot);
- return handler.SendHeldItemChange(slot);
+ return InvokeOnMainThread(() =>
+ {
+ CurrentSlot = Convert.ToByte(slot);
+ return handler.SendHeldItemChange(slot);
+ });
}
else return false;
}
@@ -1590,7 +1631,7 @@ namespace MinecraftClient
public bool UpdateSign(Location location, string line1, string line2, string line3, string line4)
{
// TODO Open sign editor first https://wiki.vg/Protocol#Open_Sign_Editor
- return handler.SendUpdateSign(location, line1, line2, line3, line4);
+ return InvokeOnMainThread(() => handler.SendUpdateSign(location, line1, line2, line3, line4));
}
///
@@ -1599,7 +1640,7 @@ namespace MinecraftClient
/// The slot of the trade, starts at 0.
public bool SelectTrade(int selectedSlot)
{
- return handler.SelectTrade(selectedSlot);
+ return InvokeOnMainThread(() => handler.SelectTrade(selectedSlot));
}
///
@@ -1611,7 +1652,7 @@ namespace MinecraftClient
/// command block flags
public bool UpdateCommandBlock(Location location, string command, CommandBlockMode mode, CommandBlockFlags flags)
{
- return handler.UpdateCommandBlock(location, command, mode, flags);
+ return InvokeOnMainThread(() => handler.UpdateCommandBlock(location, command, mode, flags));
}
#endregion
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index 8850677f..b4c605de 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -222,6 +222,7 @@
+
diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs
index 5018080b..e3c0616c 100644
--- a/MinecraftClient/Protocol/Handlers/Protocol18.cs
+++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs
@@ -423,7 +423,7 @@ namespace MinecraftClient.Protocol.Handlers
int compressedDataSize = dataTypes.ReadNextInt(packetData);
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
byte[] decompressed = ZlibUtils.Decompress(compressed);
- new Thread(() => {
+ new Task(() => {
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed));
}).Start();
}
@@ -449,7 +449,7 @@ namespace MinecraftClient.Protocol.Handlers
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
}
int dataSize = dataTypes.ReadNextVarInt(packetData);
- new Thread(() => {
+ new Task(() => {
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
}).Start();
}
diff --git a/MinecraftClient/TaskWithDelay.cs b/MinecraftClient/TaskWithDelay.cs
new file mode 100644
index 00000000..55b09495
--- /dev/null
+++ b/MinecraftClient/TaskWithDelay.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// Holds a task with delay
+ ///
+ class TaskWithDelay
+ {
+ private Action _task;
+ private int tickCounter;
+ private DateTime dateToLaunch;
+
+ public Action Task { get { return _task; } }
+
+ public TaskWithDelay(Action task, int delayTicks)
+ {
+ _task = task;
+ tickCounter = delayTicks;
+ dateToLaunch = DateTime.MaxValue;
+ }
+
+ public TaskWithDelay(Action task, TimeSpan delay)
+ {
+ _task = task;
+ tickCounter = int.MaxValue;
+ dateToLaunch = DateTime.Now + delay;
+ }
+
+ ///
+ /// Tick the counter
+ ///
+ /// Return true if the task should run now
+ public bool Tick()
+ {
+ tickCounter--;
+ if (tickCounter <= 0 || dateToLaunch < DateTime.Now)
+ return true;
+ return false;
+ }
+ }
+}