Finish AutoCraft core functional part

This commit is contained in:
ReinforceZwei 2020-07-09 22:21:39 +08:00 committed by ORelio
parent 82fb081828
commit 97d7325939
5 changed files with 220 additions and 52 deletions

View file

@ -1181,6 +1181,16 @@ namespace MinecraftClient
return Handler.RegisterCommand(cmdName, cmdDesc, callback); return Handler.RegisterCommand(cmdName, cmdDesc, callback);
} }
/// <summary>
/// Close a opened inventory
/// </summary>
/// <param name="inventoryID"></param>
/// <returns>True if success</returns>
protected bool CloseInventory(int inventoryID)
{
return Handler.CloseInventory(inventoryID);
}
/// <summary> /// <summary>
/// Command runner definition. /// Command runner definition.
/// Returned string will be the output of the command /// Returned string will be the output of the command

View file

@ -9,21 +9,86 @@ namespace MinecraftClient.ChatBots
{ {
class AutoCarft : ChatBot class AutoCarft : ChatBot
{ {
private bool waitingForResult = false; private bool waitingForUpdate = false;
private int inventoryInUse; private int inventoryInUse = -2;
private int index = 0;
private Recipe recipeInUse;
private int updateDebounce = 0;
private bool craftingFailed = false;
private enum ActionType private enum ActionType
{ {
MoveTo, LeftClick,
ShiftClick,
WaitForUpdate, WaitForUpdate,
ResetCraftArea,
Repeat Repeat
} }
private class ActionStep private class ActionStep
{ {
public ActionType Action; public ActionType ActionType;
public int Slot; public int Slot = -2;
public int InventoryID; public int InventoryID = -2;
public ItemType ItemType;
public ActionStep(ActionType actionType)
{
ActionType = actionType;
}
public ActionStep(ActionType actionType, int inventoryID)
{
ActionType = actionType;
InventoryID = inventoryID;
}
public ActionStep(ActionType actionType, int inventoryID, int slot)
{
ActionType = actionType;
Slot = slot;
InventoryID = inventoryID;
}
public ActionStep(ActionType actionType, int inventoryID, ItemType itemType)
{
ActionType = actionType;
InventoryID = inventoryID;
ItemType = itemType;
}
}
private List<ActionStep> actionSteps = new List<ActionStep>();
private class Recipe
{
public ItemType ResultItem;
public ContainerType CraftingAreaType;
public Dictionary<int, ItemType> Materials;
public Recipe(Dictionary<int, ItemType> materials, ItemType resultItem, ContainerType type)
{
Materials = materials;
ResultItem = resultItem;
CraftingAreaType = type;
}
public static Recipe ConvertToCraftingTable(Recipe recipe)
{
if (recipe.CraftingAreaType == ContainerType.PlayerInventory)
{
if (recipe.Materials.ContainsKey(4))
{
recipe.Materials[5] = recipe.Materials[4];
recipe.Materials.Remove(4);
}
if (recipe.Materials.ContainsKey(3))
{
recipe.Materials[4] = recipe.Materials[3];
recipe.Materials.Remove(3);
}
}
return recipe;
}
} }
public override void Initialize() public override void Initialize()
@ -55,64 +120,134 @@ namespace MinecraftClient.ChatBots
public string CraftCommand(string command, string[] args) public string CraftCommand(string command, string[] args)
{ {
Dictionary<int, ItemType> recipe = new Dictionary<int, ItemType> Dictionary<int, ItemType> materials = new Dictionary<int, ItemType>
{ {
{ 1, ItemType.Stone } { 1, ItemType.Coal },
{ 3, ItemType.Stick }
}; };
var inventory = GetInventories()[0]; Recipe recipe = new Recipe(materials, ItemType.StoneButton, ContainerType.PlayerInventory);
int slotToPut = -2;
int slotToTake = -2;
inventoryInUse = 0; inventoryInUse = 0;
foreach (KeyValuePair<int, ItemType> slot in recipe)
recipeInUse = recipe;
craftingFailed = false;
waitingForUpdate = false;
index = 0;
var inventory = GetInventories()[inventoryInUse];
foreach (KeyValuePair<int, ItemType> slot in recipe.Materials)
{ {
slotToPut = slot.Key + 1; actionSteps.Add(new ActionStep(ActionType.LeftClick, inventoryInUse, slot.Value));
slotToTake = -2; actionSteps.Add(new ActionStep(ActionType.LeftClick, inventoryInUse, slot.Key));
// Find material in our inventory
foreach (KeyValuePair<int, Item> item in inventory.Items)
{
if (slot.Value == item.Value.Type)
{
slotToTake = item.Key;
break;
}
}
if (slotToTake != -2)
{
// move found material to correct crafting slot
WindowAction(0, slotToTake, WindowActionType.LeftClick);
WindowAction(0, slotToPut, WindowActionType.LeftClick);
}
} }
if (slotToPut != -2 && slotToTake != -2) if (actionSteps.Count > 0)
{ {
waitingForResult = true; actionSteps.Add(new ActionStep(ActionType.WaitForUpdate, inventoryInUse, 0));
// Now wait for server to update the slot 0, craft result actionSteps.Add(new ActionStep(ActionType.ShiftClick, inventoryInUse, 0));
return "Waiting for result"; actionSteps.Add(new ActionStep(ActionType.WaitForUpdate, inventoryInUse));
actionSteps.Add(new ActionStep(ActionType.Repeat));
HandleNextStep();
return "AutoCraft start!";
} }
else return "Failed before waiting for result"; else return "AutoCraft cannot be started. Check your available materials";
} }
public override void OnInventoryUpdate(int inventoryId) public override void OnInventoryUpdate(int inventoryId)
{ {
ConsoleIO.WriteLine("Inventory " + inventoryId + " is being updated"); if (waitingForUpdate && inventoryInUse == inventoryId)
if (waitingForResult && inventoryInUse == inventoryId)
{ {
var inventory = GetInventories()[inventoryId]; updateDebounce = 2;
if (inventory.Items.ContainsKey(0)) }
}
public override void Update()
{
if (updateDebounce > 0)
{
updateDebounce--;
if (updateDebounce <= 0)
InventoryUpdateFinished();
}
}
private void InventoryUpdateFinished()
{
waitingForUpdate = false;
HandleNextStep();
}
private void HandleNextStep()
{
while (actionSteps.Count > 0)
{
if (waitingForUpdate) break;
ActionStep step = actionSteps[index];
index++;
switch (step.ActionType)
{ {
// slot 0 have item, click on it case ActionType.LeftClick:
WindowAction(0, 0, WindowActionType.LeftClick); if (step.Slot != -2)
// Now wait for server to update our inventory {
ConsoleIO.WriteLine("Crafting success"); WindowAction(step.InventoryID, step.Slot, WindowActionType.LeftClick);
} }
else if (inventory.Items.ContainsKey(-1)) else
{ {
// Server have updated our cursor to the item we want to take out from craft result int[] slots = GetInventories()[step.InventoryID].SearchItem(step.ItemType);
// Now put the item back to our inventory if (slots.Count() > 0)
WindowAction(0, 37, WindowActionType.LeftClick); {
ConsoleIO.WriteLine("Moved crafted item to inventory"); int ignoredSlot;
waitingForResult = false; if (recipeInUse.CraftingAreaType == ContainerType.PlayerInventory)
ignoredSlot = 9;
else
ignoredSlot = 10;
slots = slots.Where(slot => slot >= ignoredSlot).ToArray();
if (slots.Count() > 0)
WindowAction(step.InventoryID, slots[0], WindowActionType.LeftClick);
else
craftingFailed = true;
}
else craftingFailed = true;
}
break;
case ActionType.ShiftClick:
if (step.Slot == 0)
{
WindowAction(step.InventoryID, step.Slot, WindowActionType.ShiftClick);
}
else craftingFailed = true;
break;
case ActionType.WaitForUpdate:
if (step.InventoryID != -2)
{
waitingForUpdate = true;
}
else craftingFailed = true;
break;
case ActionType.ResetCraftArea:
if (step.InventoryID != -2)
CloseInventory(step.InventoryID);
else
craftingFailed = true;
break;
case ActionType.Repeat:
index = 0;
break;
} }
HandleError();
}
}
private void HandleError()
{
if (craftingFailed)
{
actionSteps.Clear();
CloseInventory(inventoryInUse);
ConsoleIO.WriteLogLine("Crafting aborted! Check your available materials.");
} }
} }
} }

View file

@ -153,5 +153,24 @@ namespace MinecraftClient.Inventory
default: return ContainerType.Unknown; default: return ContainerType.Unknown;
} }
} }
/// <summary>
/// Search an item in the container
/// </summary>
/// <param name="itemType">The item to search</param>
/// <returns>An array of slot ID</returns>
public int[] SearchItem(ItemType itemType)
{
List<int> result = new List<int>();
if (Items != null)
{
foreach (var item in Items)
{
if (item.Value.Type == itemType)
result.Add(item.Key);
}
}
return result.ToArray();
}
} }
} }

View file

@ -1113,6 +1113,7 @@ namespace MinecraftClient
} }
break; break;
case WindowActionType.ShiftClick: case WindowActionType.ShiftClick:
if (slotId == 0) break;
if (inventory.Items.ContainsKey(slotId)) if (inventory.Items.ContainsKey(slotId))
{ {
/* Target slot have item */ /* Target slot have item */
@ -1192,11 +1193,13 @@ namespace MinecraftClient
/// </summary> /// </summary>
/// <param name="windowId">Window ID</param> /// <param name="windowId">Window ID</param>
/// <returns>TRUE if the window was successfully closed</returns> /// <returns>TRUE if the window was successfully closed</returns>
/// <remarks>Sending close window for inventory 0 can cause server to update our inventory if there are any item in the crafting area</remarks>
public bool CloseInventory(int windowId) public bool CloseInventory(int windowId)
{ {
if (windowId != 0 && inventories.ContainsKey(windowId)) if (inventories.ContainsKey(windowId))
{ {
inventories.Remove(windowId); if (windowId != 0)
inventories.Remove(windowId);
return handler.SendCloseWindow(windowId); return handler.SendCloseWindow(windowId);
} }
return false; return false;

View file

@ -1573,6 +1573,7 @@ namespace MinecraftClient.Protocol.Handlers
case WindowActionType.LeftClick: button = 0; break; case WindowActionType.LeftClick: button = 0; break;
case WindowActionType.RightClick: button = 1; break; case WindowActionType.RightClick: button = 1; break;
case WindowActionType.MiddleClick: button = 2; mode = 3; break; case WindowActionType.MiddleClick: button = 2; mode = 3; break;
case WindowActionType.ShiftClick: button = 0; mode = 1; break;
case WindowActionType.DropItem: case WindowActionType.DropItem:
button = 0; button = 0;
mode = 4; mode = 4;