2020-07-07 11:38:15 +08:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2020-07-19 21:27:01 +08:00
using System.IO ;
2020-07-07 11:38:15 +08:00
using MinecraftClient.Inventory ;
2020-07-09 14:30:24 +08:00
using MinecraftClient.Mapping ;
2020-07-07 11:38:15 +08:00
namespace MinecraftClient.ChatBots
{
2020-07-23 20:32:02 +08:00
class AutoCraft : ChatBot
2020-07-07 11:38:15 +08:00
{
2020-07-20 19:21:52 +08:00
private bool waitingForMaterials = false ;
2020-07-09 22:21:39 +08:00
private bool waitingForUpdate = false ;
2020-07-20 19:21:52 +08:00
private bool waitingForTable = false ;
2020-07-19 21:27:01 +08:00
private bool craftingFailed = false ;
2020-07-09 22:21:39 +08:00
private int inventoryInUse = - 2 ;
private int index = 0 ;
private Recipe recipeInUse ;
2020-07-19 21:27:01 +08:00
private List < ActionStep > actionSteps = new List < ActionStep > ( ) ;
2020-07-09 22:21:39 +08:00
2020-07-20 19:21:52 +08:00
private Location tableLocation = new Location ( ) ;
private bool abortOnFailure = true ;
private int updateDebounceValue = 2 ;
2020-07-09 22:21:39 +08:00
private int updateDebounce = 0 ;
2020-07-20 19:21:52 +08:00
private int updateTimeoutValue = 10 ;
private int updateTimeout = 0 ;
private string timeoutAction = "unspecified" ;
2020-07-09 22:21:39 +08:00
2020-07-19 21:27:01 +08:00
private string configPath = @"autocraft\config.ini" ;
2020-08-11 15:34:07 +08:00
private string lastRecipe = "" ; // Used in parsing recipe config
2020-07-07 11:38:15 +08:00
2020-07-19 21:27:01 +08:00
private Dictionary < string , Recipe > recipes = new Dictionary < string , Recipe > ( ) ;
2020-07-12 19:21:30 +08:00
2020-07-20 19:21:52 +08:00
private void resetVar ( )
{
craftingFailed = false ;
waitingForTable = false ;
waitingForUpdate = false ;
waitingForMaterials = false ;
inventoryInUse = - 2 ;
index = 0 ;
recipeInUse = null ;
actionSteps . Clear ( ) ;
}
2020-07-09 14:30:24 +08:00
private enum ActionType
{
2020-07-09 22:21:39 +08:00
LeftClick ,
ShiftClick ,
2020-07-09 14:30:24 +08:00
WaitForUpdate ,
2020-07-09 22:21:39 +08:00
ResetCraftArea ,
2020-07-09 14:30:24 +08:00
Repeat
}
2020-07-12 19:21:30 +08:00
/// <summary>
/// Represent a single action step of the whole crafting process
/// </summary>
2020-07-09 14:30:24 +08:00
private class ActionStep
{
2020-07-12 19:21:30 +08:00
/// <summary>
/// The action type of this action step
/// </summary>
2020-07-09 22:21:39 +08:00
public ActionType ActionType ;
2020-07-12 19:21:30 +08:00
/// <summary>
/// For storing data needed for processing
/// </summary>
/// <remarks>-2 mean not used</remarks>
2020-07-09 22:21:39 +08:00
public int Slot = - 2 ;
2020-07-12 19:21:30 +08:00
/// <summary>
/// For storing data needed for processing
/// </summary>
/// <remarks>-2 mean not used</remarks>
2020-07-09 22:21:39 +08:00
public int InventoryID = - 2 ;
2020-07-12 19:21:30 +08:00
/// <summary>
/// For storing data needed for processing
/// </summary>
2020-07-09 22:21:39 +08:00
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 ;
}
}
2020-07-12 19:21:30 +08:00
/// <summary>
/// Represent a crafting recipe
/// </summary>
2020-07-09 22:21:39 +08:00
private class Recipe
{
2020-07-12 19:21:30 +08:00
/// <summary>
/// The results item of this recipe
/// </summary>
2020-07-09 22:21:39 +08:00
public ItemType ResultItem ;
2020-07-12 19:21:30 +08:00
/// <summary>
/// Crafting table required for this recipe, playerInventory or Crafting
/// </summary>
2020-07-09 22:21:39 +08:00
public ContainerType CraftingAreaType ;
2020-07-12 19:21:30 +08:00
/// <summary>
/// Materials needed and their position
/// </summary>
/// <remarks>position start with 1, from left to right, top to bottom</remarks>
2020-07-09 22:21:39 +08:00
public Dictionary < int , ItemType > Materials ;
2020-07-19 21:27:01 +08:00
public Recipe ( ) { }
2020-07-09 22:21:39 +08:00
public Recipe ( Dictionary < int , ItemType > materials , ItemType resultItem , ContainerType type )
{
Materials = materials ;
ResultItem = resultItem ;
CraftingAreaType = type ;
}
2020-07-12 19:21:30 +08:00
/// <summary>
/// Convert the position of a defined recipe from playerInventory to Crafting
/// </summary>
/// <param name="recipe"></param>
/// <returns>Converted recipe</returns>
/// <remarks>so that it can be used in crafting table</remarks>
2020-07-09 22:21:39 +08:00
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 ;
}
2020-07-09 14:30:24 +08:00
}
2020-07-23 20:32:02 +08:00
public AutoCraft ( string configPath = @"autocraft\config.ini" )
{
this . configPath = configPath ;
}
2020-07-07 11:38:15 +08:00
public override void Initialize ( )
{
2020-07-20 19:33:57 +08:00
if ( ! GetInventoryEnabled ( ) )
{
2020-07-23 20:32:02 +08:00
LogToConsole ( "Inventory handling is disabled. AutoCraft will be unloaded" ) ;
2020-07-20 19:33:57 +08:00
UnloadBot ( ) ;
}
2020-07-23 20:32:02 +08:00
RegisterChatBotCommand ( "autocraft" , "Auto-crafting ChatBot command" , CommandHandler ) ;
RegisterChatBotCommand ( "ac" , "Auto-crafting ChatBot command alias" , CommandHandler ) ;
2020-07-20 19:33:57 +08:00
LoadConfig ( ) ;
2020-07-19 21:27:01 +08:00
}
public string CommandHandler ( string cmd , string [ ] args )
{
2020-07-20 19:21:52 +08:00
if ( args . Length > 0 )
2020-07-19 21:27:01 +08:00
{
2020-07-20 19:21:52 +08:00
switch ( args [ 0 ] )
{
case "load" :
LoadConfig ( ) ;
return "" ;
case "list" :
string names = string . Join ( ", " , recipes . Keys . ToList ( ) ) ;
return String . Format ( "Total {0} recipes loaded: {1}" , recipes . Count , names ) ;
case "reload" :
recipes . Clear ( ) ;
LoadConfig ( ) ;
return "" ;
case "resetcfg" :
WriteDefaultConfig ( ) ;
2020-07-23 20:32:02 +08:00
return "Resetting your config to default" ;
2020-07-20 19:21:52 +08:00
case "start" :
if ( args . Length > = 2 )
{
string name = args [ 1 ] ;
if ( recipes . ContainsKey ( name ) )
{
resetVar ( ) ;
PrepareCrafting ( recipes [ name ] ) ;
return "" ;
}
2020-07-23 20:32:02 +08:00
else return "Specified recipe name does not exist. Check your config file." ;
2020-07-20 19:21:52 +08:00
}
2020-07-23 20:32:02 +08:00
else return "Please specify the recipe name you want to craft." ;
2020-07-20 19:21:52 +08:00
case "stop" :
StopCrafting ( ) ;
return "AutoCraft stopped" ;
case "help" :
return GetCommandHelp ( args . Length > = 2 ? args [ 1 ] : "" ) ;
default :
return GetHelp ( ) ;
}
2020-07-19 21:27:01 +08:00
}
2020-07-20 19:21:52 +08:00
else return GetHelp ( ) ;
2020-07-19 21:27:01 +08:00
}
2020-07-20 19:21:52 +08:00
private string GetHelp ( )
2020-07-19 21:27:01 +08:00
{
2020-07-23 20:32:02 +08:00
return "Available commands: load, list, reload, resetcfg, start, stop, help. Use /autocraft help <cmd name> for more information. You may use /ac as command alias." ;
2020-07-07 11:38:15 +08:00
}
2020-07-20 19:21:52 +08:00
private string GetCommandHelp ( string cmd )
2020-07-07 11:38:15 +08:00
{
2020-07-20 19:21:52 +08:00
switch ( cmd . ToLower ( ) )
2020-07-07 11:38:15 +08:00
{
2020-07-20 19:21:52 +08:00
case "load" :
2020-07-23 20:32:02 +08:00
return "Load the config file." ;
2020-07-20 19:21:52 +08:00
case "list" :
return "List loaded recipes name." ;
case "reload" :
2020-07-23 20:32:02 +08:00
return "Reload the config file." ;
2020-07-20 19:21:52 +08:00
case "resetcfg" :
return "Write the default example config to default location." ;
case "start" :
return "Start the crafting. Usage: /autocraft start <recipe name>" ;
case "stop" :
return "Stop the current running crafting process" ;
case "help" :
return "Get the command description. Usage: /autocraft help <command name>" ;
default :
return GetHelp ( ) ;
2020-07-07 11:38:15 +08:00
}
2020-07-19 21:27:01 +08:00
}
#region Config handling
public void LoadConfig ( )
{
if ( ! File . Exists ( configPath ) )
{
if ( ! Directory . Exists ( configPath ) )
{
Directory . CreateDirectory ( @"autocraft" ) ;
}
WriteDefaultConfig ( ) ;
2020-07-23 20:32:02 +08:00
LogDebugToConsole ( "No config found. Writing a new one." ) ;
2020-07-19 21:27:01 +08:00
}
try
{
ParseConfig ( ) ;
2020-07-23 20:32:02 +08:00
LogToConsole ( "Successfully loaded" ) ;
2020-07-19 21:27:01 +08:00
}
catch ( Exception e )
{
2020-07-23 20:32:02 +08:00
LogToConsole ( "Error while parsing config: \n" + e . Message ) ;
2020-07-19 21:27:01 +08:00
}
}
private void WriteDefaultConfig ( )
{
string [ ] content =
{
2020-07-23 20:32:02 +08:00
"[AutoCraft]" ,
"# A valid autocraft config must begin with [AutoCraft]" ,
2020-07-20 19:21:52 +08:00
"" ,
"tablelocation=0,65,0 # Location of the crafting table if you intended to use it. Terrain and movements must be enabled. Format: x,y,z" ,
"onfailure=abort # What to do on crafting failure, abort or wait" ,
"" ,
2020-07-23 20:32:02 +08:00
"# You can define multiple recipes in a single config file" ,
2020-07-20 19:21:52 +08:00
"# This is an example of how to define a recipe" ,
2020-07-23 20:32:02 +08:00
"[Recipe]" ,
"name=whatever # name could be whatever you like. This field must be defined first" ,
2020-07-20 19:21:52 +08:00
"type=player # crafting table type: player or table" ,
"result=StoneButton # the resulting item" ,
"" ,
2020-07-19 21:27:01 +08:00
"# define slots with their deserved item" ,
2020-07-20 19:21:52 +08:00
"slot1=Stone # slot start with 1, count from left to right, top to bottom" ,
2020-07-19 21:27:01 +08:00
"# For the naming of the items, please see" ,
"# https://github.com/ORelio/Minecraft-Console-Client/blob/master/MinecraftClient/Inventory/ItemType.cs"
} ;
File . WriteAllLines ( configPath , content ) ;
}
private void ParseConfig ( )
{
string [ ] content = File . ReadAllLines ( configPath ) ;
2020-07-23 20:32:02 +08:00
if ( content . Length < = 0 )
{
throw new Exception ( "Empty onfiguration file: " + configPath ) ;
}
if ( content [ 0 ] . ToLower ( ) ! = "[autocraft]" )
2020-07-19 21:27:01 +08:00
{
2020-07-23 20:32:02 +08:00
throw new Exception ( "Invalid configuration file: " + configPath ) ;
2020-07-19 21:27:01 +08:00
}
// local variable for use in parsing config
2020-07-23 20:32:02 +08:00
string section = "" ;
2020-07-19 21:27:01 +08:00
Dictionary < string , Recipe > recipes = new Dictionary < string , Recipe > ( ) ;
string lastRecipe = "" ;
foreach ( string l in content )
{
// ignore comment start with #
2020-07-23 20:32:02 +08:00
if ( l . StartsWith ( "#" ) )
continue ;
2020-07-19 21:27:01 +08:00
string line = l . Split ( '#' ) [ 0 ] . Trim ( ) ;
2020-07-23 20:32:02 +08:00
if ( line . Length < = 0 )
continue ;
2020-07-19 21:27:01 +08:00
if ( line [ 0 ] = = '[' & & line [ line . Length - 1 ] = = ']' )
{
2020-07-23 20:32:02 +08:00
section = line . Substring ( 1 , line . Length - 2 ) . ToLower ( ) ;
2020-07-19 21:27:01 +08:00
continue ;
}
string key = line . Split ( '=' ) [ 0 ] . ToLower ( ) ;
2020-07-23 20:32:02 +08:00
if ( ! ( line . Length > ( key . Length + 1 ) ) )
continue ;
2020-07-19 21:27:01 +08:00
string value = line . Substring ( key . Length + 1 ) ;
2020-07-23 20:32:02 +08:00
switch ( section )
2020-07-19 21:27:01 +08:00
{
2020-08-11 15:34:07 +08:00
case "recipe" : parseRecipe ( key , value ) ; break ;
2020-07-20 19:21:52 +08:00
case "autocraft" : parseMain ( key , value ) ; break ;
2020-07-19 21:27:01 +08:00
}
}
// check and save recipe
2020-07-23 20:32:02 +08:00
foreach ( var pair in recipes )
2020-07-19 21:27:01 +08:00
{
if ( ( pair . Value . CraftingAreaType = = ContainerType . PlayerInventory
| | pair . Value . CraftingAreaType = = ContainerType . Crafting )
& & ( pair . Value . Materials ! = null
& & pair . Value . Materials . Count > 0 )
& & pair . Value . ResultItem ! = ItemType . Air )
{
// checking pass
this . recipes . Add ( pair . Key , pair . Value ) ;
}
else
{
2020-07-23 20:32:02 +08:00
throw new Exception ( "Missing item in recipe: " + pair . Key ) ;
2020-07-19 21:27:01 +08:00
}
}
2020-07-24 10:51:35 +08:00
}
#region Method for parsing different section of config
2020-07-19 21:27:01 +08:00
2020-07-24 10:51:35 +08:00
private void parseMain ( string key , string value )
{
switch ( key )
2020-07-20 19:21:52 +08:00
{
2020-07-24 10:51:35 +08:00
case "tablelocation" :
string [ ] values = value . Split ( ',' ) ;
if ( values . Length = = 3 )
{
tableLocation . X = Convert . ToInt32 ( values [ 0 ] ) ;
tableLocation . Y = Convert . ToInt32 ( values [ 1 ] ) ;
tableLocation . Z = Convert . ToInt32 ( values [ 2 ] ) ;
}
else throw new Exception ( "Invalid tablelocation format: " + key ) ;
break ;
case "onfailure" :
abortOnFailure = value . ToLower ( ) = = "abort" ? true : false ;
break ;
case "updatedebounce" :
updateDebounceValue = Convert . ToInt32 ( value ) ;
break ;
2020-07-20 19:21:52 +08:00
}
2020-07-24 10:51:35 +08:00
}
2020-07-20 19:21:52 +08:00
2020-08-11 15:34:07 +08:00
private void parseRecipe ( string key , string value )
2020-07-24 10:51:35 +08:00
{
if ( key . StartsWith ( "slot" ) )
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
int slot = Convert . ToInt32 ( key [ key . Length - 1 ] . ToString ( ) ) ;
if ( slot > 0 & & slot < 10 )
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
if ( recipes . ContainsKey ( lastRecipe ) )
2020-07-19 21:27:01 +08:00
{
2020-07-24 12:36:21 +02:00
ItemType itemType ;
2020-07-31 03:06:36 +08:00
if ( Enum . TryParse ( value , true , out itemType ) )
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
if ( recipes [ lastRecipe ] . Materials ! = null & & recipes [ lastRecipe ] . Materials . Count > 0 )
{
recipes [ lastRecipe ] . Materials . Add ( slot , itemType ) ;
}
else
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
recipes [ lastRecipe ] . Materials = new Dictionary < int , ItemType > ( )
2020-07-19 21:27:01 +08:00
{
{ slot , itemType }
} ;
}
2020-07-24 10:51:35 +08:00
return ;
2020-07-19 21:27:01 +08:00
}
2020-08-11 15:34:07 +08:00
else
{
throw new Exception ( "Invalid item name in recipe " + lastRecipe + " at " + key ) ;
}
}
else
{
throw new Exception ( "Missing recipe name while parsing a recipe" ) ;
2020-07-19 21:27:01 +08:00
}
}
2020-08-11 15:34:07 +08:00
else
{
throw new Exception ( "Invalid slot field in recipe: " + key ) ;
}
2020-07-24 10:51:35 +08:00
}
else
{
switch ( key )
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
case "name" :
if ( ! recipes . ContainsKey ( value ) )
{
recipes . Add ( value , new Recipe ( ) ) ;
lastRecipe = value ;
}
else
{
throw new Exception ( "Duplicate recipe name specified: " + value ) ;
}
break ;
case "type" :
if ( recipes . ContainsKey ( lastRecipe ) )
{
recipes [ lastRecipe ] . CraftingAreaType = value . ToLower ( ) = = "player" ? ContainerType . PlayerInventory : ContainerType . Crafting ;
}
break ;
case "result" :
if ( recipes . ContainsKey ( lastRecipe ) )
{
2020-07-24 12:36:21 +02:00
ItemType itemType ;
2020-07-31 03:06:36 +08:00
if ( Enum . TryParse ( value , true , out itemType ) )
2020-07-19 21:27:01 +08:00
{
2020-07-24 10:51:35 +08:00
recipes [ lastRecipe ] . ResultItem = itemType ;
2020-07-19 21:27:01 +08:00
}
2020-07-24 10:51:35 +08:00
}
break ;
2020-07-19 21:27:01 +08:00
}
}
}
2020-07-24 10:51:35 +08:00
#endregion
2020-07-19 21:27:01 +08:00
#endregion
#region Core part of auto - crafting
2020-07-07 11:38:15 +08:00
public override void OnInventoryUpdate ( int inventoryId )
{
2020-07-20 19:21:52 +08:00
if ( ( waitingForUpdate & & inventoryInUse = = inventoryId ) | | ( waitingForMaterials & & inventoryInUse = = inventoryId ) )
2020-07-07 11:38:15 +08:00
{
2020-07-12 19:21:30 +08:00
// Because server might send us a LOT of update at once, even there is only a single slot updated.
// Using this to make sure we don't do things before inventory update finish
2020-07-20 19:21:52 +08:00
updateDebounce = updateDebounceValue ;
}
}
public override void OnInventoryOpen ( int inventoryId )
{
if ( waitingForTable )
{
if ( GetInventories ( ) [ inventoryId ] . Type = = ContainerType . Crafting )
{
waitingForTable = false ;
ClearTimeout ( ) ;
// After table opened, we need to wait for server to update table inventory items
waitingForUpdate = true ;
inventoryInUse = inventoryId ;
PrepareCrafting ( recipeInUse ) ;
}
2020-07-09 22:21:39 +08:00
}
}
public override void Update ( )
{
if ( updateDebounce > 0 )
{
updateDebounce - - ;
if ( updateDebounce < = 0 )
InventoryUpdateFinished ( ) ;
}
2020-07-20 19:21:52 +08:00
if ( updateTimeout > 0 )
{
updateTimeout - - ;
if ( updateTimeout < = 0 )
HandleUpdateTimeout ( ) ;
}
2020-07-09 22:21:39 +08:00
}
private void InventoryUpdateFinished ( )
{
2020-07-20 19:21:52 +08:00
if ( waitingForUpdate | | waitingForMaterials )
{
if ( waitingForUpdate )
waitingForUpdate = false ;
if ( waitingForMaterials )
{
waitingForMaterials = false ;
craftingFailed = false ;
}
HandleNextStep ( ) ;
}
}
private void OpenTable ( Location location )
{
SendPlaceBlock ( location , Direction . Up ) ;
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Prepare the crafting action steps by the given recipe name and start crafting
/// </summary>
/// <param name="recipe">Name of the recipe to craft</param>
2020-07-20 19:21:52 +08:00
private void PrepareCrafting ( string name )
{
PrepareCrafting ( recipes [ name ] ) ;
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Prepare the crafting action steps by the given recipe and start crafting
/// </summary>
/// <param name="recipe">Recipe to craft</param>
2020-07-20 19:21:52 +08:00
private void PrepareCrafting ( Recipe recipe )
{
recipeInUse = recipe ;
if ( recipeInUse . CraftingAreaType = = ContainerType . PlayerInventory )
inventoryInUse = 0 ;
else
{
var inventories = GetInventories ( ) ;
foreach ( var inventory in inventories )
if ( inventory . Value . Type = = ContainerType . Crafting )
inventoryInUse = inventory . Key ;
if ( inventoryInUse = = - 2 )
{
// table required but not found. Try to open one
OpenTable ( tableLocation ) ;
waitingForTable = true ;
SetTimeout ( "table not found" ) ;
return ;
}
}
foreach ( KeyValuePair < int , ItemType > slot in recipe . Materials )
{
// Steps for moving items from inventory to crafting area
actionSteps . Add ( new ActionStep ( ActionType . LeftClick , inventoryInUse , slot . Value ) ) ;
actionSteps . Add ( new ActionStep ( ActionType . LeftClick , inventoryInUse , slot . Key ) ) ;
}
if ( actionSteps . Count > 0 )
{
// Wait for server to send us the crafting result
actionSteps . Add ( new ActionStep ( ActionType . WaitForUpdate , inventoryInUse , 0 ) ) ;
// Put item back to inventory. (Using shift-click can take all item at once)
actionSteps . Add ( new ActionStep ( ActionType . ShiftClick , inventoryInUse , 0 ) ) ;
// We need to wait for server to update us after taking item from crafting result
actionSteps . Add ( new ActionStep ( ActionType . WaitForUpdate , inventoryInUse ) ) ;
// Repeat the whole process again
actionSteps . Add ( new ActionStep ( ActionType . Repeat ) ) ;
// Start crafting
2020-07-23 20:32:02 +08:00
ConsoleIO . WriteLogLine ( "Starting AutoCraft: " + recipe . ResultItem ) ;
2020-07-20 19:21:52 +08:00
HandleNextStep ( ) ;
}
2020-07-23 20:32:02 +08:00
else ConsoleIO . WriteLogLine ( "AutoCraft cannot be started. Check your available materials for crafting " + recipe . ResultItem ) ;
2020-07-20 19:21:52 +08:00
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Stop the crafting process by clearing crafting action steps and close the inventory
/// </summary>
2020-07-20 19:21:52 +08:00
private void StopCrafting ( )
{
actionSteps . Clear ( ) ;
// Closing inventory can make server to update our inventory
// Useful when
// - There are some items left in the crafting area
// - Resynchronize player inventory if using crafting table
if ( GetInventories ( ) . ContainsKey ( inventoryInUse ) )
{
CloseInventory ( inventoryInUse ) ;
ConsoleIO . WriteLogLine ( "Inventory #" + inventoryInUse + " was closed by AutoCraft" ) ;
}
2020-07-09 22:21:39 +08:00
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Handle next crafting action step
/// </summary>
2020-07-09 22:21:39 +08:00
private void HandleNextStep ( )
{
while ( actionSteps . Count > 0 )
{
2020-07-20 19:21:52 +08:00
if ( waitingForUpdate | | waitingForMaterials | | craftingFailed ) break ;
2020-07-09 22:21:39 +08:00
ActionStep step = actionSteps [ index ] ;
index + + ;
switch ( step . ActionType )
2020-07-07 11:38:15 +08:00
{
2020-07-09 22:21:39 +08:00
case ActionType . LeftClick :
if ( step . Slot ! = - 2 )
{
WindowAction ( step . InventoryID , step . Slot , WindowActionType . LeftClick ) ;
}
else
{
int [ ] slots = GetInventories ( ) [ step . InventoryID ] . SearchItem ( step . ItemType ) ;
if ( slots . Count ( ) > 0 )
{
int ignoredSlot ;
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 ;
2020-07-07 11:38:15 +08:00
}
2020-07-09 22:21:39 +08:00
HandleError ( ) ;
}
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Handle any crafting error after a step was processed
/// </summary>
2020-07-09 22:21:39 +08:00
private void HandleError ( )
{
if ( craftingFailed )
{
2020-08-11 15:34:07 +08:00
if ( actionSteps [ index - 1 ] . ActionType = = ActionType . LeftClick & & actionSteps [ index - 1 ] . ItemType ! = ItemType . Air )
{
// Inform user the missing meterial name
ConsoleIO . WriteLogLine ( "Missing material: " + actionSteps [ index - 1 ] . ItemType . ToString ( ) ) ;
}
2020-07-20 19:21:52 +08:00
if ( abortOnFailure )
{
StopCrafting ( ) ;
ConsoleIO . WriteLogLine ( "Crafting aborted! Check your available materials." ) ;
}
else
{
waitingForMaterials = true ;
// Even though crafting failed, action step index will still increase
// we want to do that failed step again so decrease index by 1
index - - ;
ConsoleIO . WriteLogLine ( "Crafting failed! Waiting for more materials" ) ;
}
2020-07-07 11:38:15 +08:00
}
}
2020-07-19 21:27:01 +08:00
2020-07-20 19:21:52 +08:00
private void HandleUpdateTimeout ( )
{
ConsoleIO . WriteLogLine ( "Action timeout! Reason: " + timeoutAction ) ;
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Set the timeout. Used to detect the failure of open crafting table
/// </summary>
/// <param name="reason">The reason to display if timeout</param>
2020-07-20 19:21:52 +08:00
private void SetTimeout ( string reason = "unspecified" )
{
updateTimeout = updateTimeoutValue ;
timeoutAction = reason ;
}
2020-07-22 19:01:43 +08:00
/// <summary>
/// Clear the timeout
/// </summary>
2020-07-20 19:21:52 +08:00
private void ClearTimeout ( )
{
updateTimeout = 0 ;
}
2020-07-19 21:27:01 +08:00
#endregion
2020-07-07 11:38:15 +08:00
}
}