From f0ff7134135c39907955c048e8406eec85976b92 Mon Sep 17 00:00:00 2001 From: austin Date: Thu, 1 May 2025 20:44:31 -0400 Subject: [PATCH] ai crafting --- docs/src/progress/renderertodo.md | 2 + .../state/inventory/InventoryUtils.java | 8 +- .../data/item/source/ItemSourcingData.java | 21 +- .../data/item/source/ItemSourcingMap.java | 2 +- .../server/protocol/InventoryProtocol.java | 160 +------------ .../server/ai/blackboard/BlackboardKeys.java | 5 + .../ai/nodes/actions/interact/CraftNode.java | 28 +++ .../checks/inventory/SourcingTypeNode.java | 24 +- .../ai/nodes/plan/SolveSourcingTreeNode.java | 28 +++ .../ai/trees/creature/AcquireItemTree.java | 20 +- .../server/player/CraftingActions.java | 216 ++++++++++++++++++ 11 files changed, 345 insertions(+), 169 deletions(-) create mode 100644 src/main/java/electrosphere/server/ai/nodes/actions/interact/CraftNode.java create mode 100644 src/main/java/electrosphere/server/player/CraftingActions.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index e24c7f0e..8fc152f7 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1634,6 +1634,8 @@ AI work Many new AI trees - AI can seek out items - AI can harvest entities + - AI can pick up items + - AI can craft diff --git a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java index 3d006ac5..248d3983 100644 --- a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java +++ b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java @@ -735,12 +735,12 @@ public class InventoryUtils { rVal.addAll(equipInventory.getItems()); } if(InventoryUtils.hasNaturalInventory(creature)){ - UnrelationalInventoryState equipInventory = InventoryUtils.getNaturalInventory(creature); - rVal.addAll(equipInventory.getItems()); + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(creature); + rVal.addAll(naturalInventory.getItems()); } if(InventoryUtils.hasToolbarInventory(creature)){ - RelationalInventoryState equipInventory = InventoryUtils.getToolbarInventory(creature); - rVal.addAll(equipInventory.getItems()); + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(creature); + rVal.addAll(toolbarInventory.getItems()); } //filter null items rVal = rVal.stream().filter((Entity el) -> {return el != null;}).collect(Collectors.toList()); diff --git a/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java b/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java index d61b1704..0942ade2 100644 --- a/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java +++ b/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java @@ -27,7 +27,16 @@ public class ItemSourcingData { * Fell a tree */ TREE, + /** + * Pick up the item off the floor + */ + PICKUP, } + + /** + * The goal item type + */ + String goalItem; /** * The list of recipes that create this item @@ -46,11 +55,13 @@ public class ItemSourcingData { /** * Constructor + * @param goalItem The item that this object stores sources of (ie if this is rocks, all the source lists should contain ways to get rocks) * @param recipes The recipes to source from * @param harvestTargets The objects to harvest from * @param trees The trees to drop from */ - public ItemSourcingData(List recipes, List harvestTargets, List trees){ + public ItemSourcingData(String goalItem, List recipes, List harvestTargets, List trees){ + this.goalItem = goalItem; this.recipes = recipes; this.harvestTargets = harvestTargets; this.trees = trees; @@ -80,6 +91,14 @@ public class ItemSourcingData { return trees; } + /** + * Gets the item that this object stores sources of (ie if this is rocks, all the source lists should contain ways to get rocks) + * @return The item type id + */ + public String getGoalItem(){ + return goalItem; + } + } diff --git a/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java b/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java index a26af031..479d34a4 100644 --- a/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java +++ b/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java @@ -100,7 +100,7 @@ public class ItemSourcingMap { } //store the sourcing data - ItemSourcingData sourcingData = new ItemSourcingData(recipes, harvestTargets, trees); + ItemSourcingData sourcingData = new ItemSourcingData(item.getId(), recipes, harvestTargets, trees); sourcingMap.itemSourcingDataMap.put(item.getId(),sourcingData); } diff --git a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java index 4ce1871a..ed192306 100644 --- a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java @@ -1,27 +1,15 @@ package electrosphere.net.server.protocol; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.state.inventory.InventoryUtils; -import electrosphere.entity.state.inventory.RelationalInventoryState; -import electrosphere.entity.state.inventory.UnrelationalInventoryState; -import electrosphere.entity.state.item.ServerChargeState; import electrosphere.game.data.crafting.RecipeData; -import electrosphere.game.data.crafting.RecipeIngredientData; -import electrosphere.game.data.item.Item; -import electrosphere.game.data.item.ItemDataMap; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.InventoryMessage; import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.template.ServerProtocolTemplate; -import electrosphere.server.datacell.ServerDataCell; -import electrosphere.server.datacell.utils.DataCellSearchUtils; import electrosphere.server.datacell.utils.EntityLookupUtils; +import electrosphere.server.player.CraftingActions; import electrosphere.server.player.PlayerActions; /** @@ -108,7 +96,7 @@ public class InventoryProtocol implements ServerProtocolTemplate reagentList = new LinkedList(); - boolean hasReagents = true; - Map ingredientTargetMap = new HashMap(); - Map ingredientAccretionMap = new HashMap(); - //find the reagents we're going to use to craft - for(RecipeIngredientData ingredient : recipe.getIngredients()){ - ingredientTargetMap.put(ingredient.getItemType(),ingredient.getCount()); - ingredientAccretionMap.put(ingredient.getItemType(),0); - int found = 0; - if(naturalInventory != null){ - for(Entity itemEnt : naturalInventory.getItems()){ - if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ - if(ServerChargeState.hasServerChargeState(itemEnt)){ - ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); - found = found + serverChargeState.getCharges(); - } else { - found++; - } - reagentList.add(itemEnt); - } - if(found >= ingredient.getCount()){ - break; - } - } - } - if(toolbarInventory != null){ - for(Entity itemEnt : toolbarInventory.getItems()){ - if(itemEnt == null){ - continue; - } - if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ - if(ServerChargeState.hasServerChargeState(itemEnt)){ - ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); - found = found + serverChargeState.getCharges(); - } else { - found++; - } - reagentList.add(itemEnt); - } - if(found >= ingredient.getCount()){ - break; - } - } - } - if(found < ingredient.getCount()){ - hasReagents = false; - break; - } - } - if(hasReagents){ - for(Entity reagentEnt : reagentList){ - - //determine how many we need to still remove - Item itemData = itemMap.getItem(reagentEnt); - int targetToRemove = ingredientTargetMap.get(itemData.getId()); - int alreadyFound = ingredientAccretionMap.get(itemData.getId()); - if(alreadyFound > targetToRemove){ - throw new Error("Removed too many ingredients! " + targetToRemove + " " + alreadyFound); - } else if(alreadyFound == targetToRemove){ - continue; - } - - - if(ServerChargeState.hasServerChargeState(reagentEnt)){ - ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(reagentEnt); - int available = serverChargeState.getCharges(); - - if(available > targetToRemove - alreadyFound){ - int chargesToRemove = targetToRemove - alreadyFound; - serverChargeState.attemptAddCharges(-chargesToRemove); - //just remove charges - ingredientAccretionMap.put(itemData.getId(),alreadyFound + chargesToRemove); - } else { - //remove item entirely (consuming all charges) - if(naturalInventory != null){ - naturalInventory.removeItem(reagentEnt); - } - if(toolbarInventory != null){ - toolbarInventory.tryRemoveItem(reagentEnt); - } - this.deleteItemInInventory(crafter, reagentEnt); - ingredientAccretionMap.put(itemData.getId(),alreadyFound + available); - } - } else { - if(naturalInventory != null){ - naturalInventory.removeItem(reagentEnt); - } - if(toolbarInventory != null){ - toolbarInventory.tryRemoveItem(reagentEnt); - } - this.deleteItemInInventory(crafter, reagentEnt); - ingredientAccretionMap.put(itemData.getId(),alreadyFound + 1); - } - } - for(RecipeIngredientData product : recipe.getProducts()){ - Item productType = itemMap.getItem(product.getItemType()); - if(productType == null){ - throw new Error("Could not locate product definition! " + productType + " " + product.getItemType()); - } - InventoryUtils.serverCreateInventoryItem(crafter, product.getItemType(), product.getCount()); - } - } - } - - - /** - * Delete an item that is in an inventory - * @param parent The parent entity that contains the in-inventory item - * @param itemEnt The item that is in an inventory - */ - private void deleteItemInInventory(Entity parent, Entity inInventory){ - //the parent entity's data cell - ServerDataCell dataCell = DataCellSearchUtils.getEntityDataCell(parent); - //broadcast destroy entity - dataCell.broadcastNetworkMessage(InventoryMessage.constructremoveItemFromInventoryMessage(inInventory.getId())); - //remove the inventories - UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(parent); - RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(parent); - if(naturalInventory != null){ - naturalInventory.removeItem(inInventory); - } - if(toolbarInventory != null){ - toolbarInventory.tryRemoveItem(inInventory); - } - } - } diff --git a/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java b/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java index d4ad5323..911abe6d 100644 --- a/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java +++ b/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java @@ -50,6 +50,11 @@ public class BlackboardKeys { */ public static final String ITEM_SOURCING_DATA = "itemSourcingData"; + /** + * The category of item to target + */ + public static final String ITEM_TARGET_CATEGORY = "itemTargetCategory"; + /** * The type of entity to try to harvest */ diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/interact/CraftNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/interact/CraftNode.java new file mode 100644 index 00000000..8faff52a --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/actions/interact/CraftNode.java @@ -0,0 +1,28 @@ +package electrosphere.server.ai.nodes.actions.interact; + +import electrosphere.entity.Entity; +import electrosphere.game.data.crafting.RecipeData; +import electrosphere.game.data.item.source.ItemSourcingData; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.plan.SolveSourcingTreeNode; +import electrosphere.server.player.CraftingActions; + +/** + * Tries to craft an item + */ +public class CraftNode implements AITreeNode { + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + ItemSourcingData sourcingData = SolveSourcingTreeNode.getItemSourcingData(blackboard); + for(RecipeData recipe : sourcingData.getRecipes()){ + if(CraftingActions.canCraft(entity, null, recipe)){ + CraftingActions.attemptCraft(entity, null, recipe); + return AITreeNodeResult.SUCCESS; + } + } + return AITreeNodeResult.FAILURE; + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java index 1c511b43..9e6bac82 100644 --- a/src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java @@ -1,11 +1,17 @@ package electrosphere.server.ai.nodes.checks.inventory; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + import electrosphere.entity.Entity; +import electrosphere.entity.types.common.CommonEntityUtils; import electrosphere.game.data.item.source.ItemSourcingData; import electrosphere.game.data.item.source.ItemSourcingData.SourcingType; import electrosphere.server.ai.blackboard.Blackboard; import electrosphere.server.ai.nodes.AITreeNode; import electrosphere.server.ai.nodes.plan.SolveSourcingTreeNode; +import electrosphere.server.ai.services.NearbyEntityService; /** * Checks if the supplied type of sourcing is the path for the current target item to acquire @@ -17,12 +23,19 @@ public class SourcingTypeNode implements AITreeNode { */ SourcingType sourcingType; + /** + * The blackboard key storing the target entity type id + */ + String targetTypeKey; + /** * Constructor + * @param targetTypeKey The blackboard key storing the target entity type id * @param sourcingType The type of sourcing to check */ - public SourcingTypeNode(SourcingType sourcingType){ + public SourcingTypeNode(SourcingType sourcingType, String targetTypeKey){ this.sourcingType = sourcingType; + this.targetTypeKey = targetTypeKey; } @Override @@ -38,6 +51,15 @@ public class SourcingTypeNode implements AITreeNode { //succeed based on the type of sourcing that this node is set for switch(this.sourcingType){ + case PICKUP: { + //see if the entity is in the vicinity + String targetEntityType = (String)blackboard.get(this.targetTypeKey); + Collection nearbyEntities = NearbyEntityService.getNearbyEntities(blackboard); + List types = nearbyEntities.stream().map((Entity ent) -> {return CommonEntityUtils.getEntitySubtype(ent);}).collect(Collectors.toList()); + if(types.contains(targetEntityType)){ + return AITreeNodeResult.SUCCESS; + } + } break; case RECIPE: { if(sourcingData.getRecipes().size() > 0){ return AITreeNodeResult.SUCCESS; diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java index 52871548..443f34d0 100644 --- a/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java @@ -46,6 +46,7 @@ public class SolveSourcingTreeNode implements AITreeNode { HarvestNode.setHarvestTargetType(blackboard, sourcingData.getHarvestTargets().get(0).getId()); } SolveSourcingTreeNode.setItemSourcingData(blackboard, sourcingData); + SolveSourcingTreeNode.setItemTargetCategory(blackboard, sourcingData.getGoalItem()); return AITreeNodeResult.SUCCESS; } @@ -103,4 +104,31 @@ public class SolveSourcingTreeNode implements AITreeNode { return (ItemSourcingData)blackboard.get(BlackboardKeys.ITEM_SOURCING_DATA); } + /** + * Sets the item target category of the blackboard + * @param blackboard The blackboard + * @param tree The data + */ + public static void setItemTargetCategory(Blackboard blackboard, String data){ + blackboard.put(BlackboardKeys.ITEM_TARGET_CATEGORY, data); + } + + /** + * Checks if the blackboard has an item target category + * @param blackboard The blackboard + * @return The item sourcing data + */ + public static boolean hasItemTargetCategory(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.ITEM_TARGET_CATEGORY); + } + + /** + * Gets the item target category of the blackboard + * @param blackboard The blackboard + * @return The item target category + */ + public static String getItemTargetCategory(Blackboard blackboard){ + return (String)blackboard.get(BlackboardKeys.ITEM_TARGET_CATEGORY); + } + } diff --git a/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java index 108d2620..6ba071bb 100644 --- a/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java +++ b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java @@ -4,6 +4,8 @@ import electrosphere.collision.CollisionEngine; import electrosphere.game.data.item.source.ItemSourcingData.SourcingType; import electrosphere.server.ai.blackboard.BlackboardKeys; import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.actions.interact.CollectItemNode; +import electrosphere.server.ai.nodes.actions.interact.CraftNode; import electrosphere.server.ai.nodes.actions.interact.HarvestNode; import electrosphere.server.ai.nodes.checks.inventory.SourcingTypeNode; import electrosphere.server.ai.nodes.meta.collections.SelectorNode; @@ -35,17 +37,27 @@ public class AcquireItemTree { //solve how we're going to get this top level item new SolveSourcingTreeNode(blackboardKey), new SelectorNode( + new SequenceNode( + new PublishStatusNode("Pick up an item"), + //check if we should be sourcing this item by picking it up + new SourcingTypeNode(SourcingType.PICKUP, BlackboardKeys.ITEM_TARGET_CATEGORY), + //logic to pick up the item + new TargetEntityCategoryNode(BlackboardKeys.ITEM_TARGET_CATEGORY), + MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET), + new CollectItemNode(), + new RunnerNode(null) + ), new SequenceNode( new PublishStatusNode("Craft an item"), //check if we should be sourcing this from a recipe - new SourcingTypeNode(SourcingType.RECIPE), - //TODO: logic to craft a recipe + new SourcingTypeNode(SourcingType.RECIPE, blackboardKey), + new CraftNode(), new RunnerNode(null) ), new SequenceNode( new PublishStatusNode("Harvest an item"), //check if we should be sourcing this from harvesting foliage - new SourcingTypeNode(SourcingType.HARVEST), + new SourcingTypeNode(SourcingType.HARVEST, blackboardKey), new TargetEntityCategoryNode(BlackboardKeys.HARVEST_TARGET_TYPE), MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET), new HarvestNode(), @@ -54,7 +66,7 @@ public class AcquireItemTree { new SequenceNode( new PublishStatusNode("Fell a tree"), //check if we should be sourcing this from felling a tree - new SourcingTypeNode(SourcingType.TREE), + new SourcingTypeNode(SourcingType.TREE, blackboardKey), //TODO: logic to fell a tree new RunnerNode(null) ) diff --git a/src/main/java/electrosphere/server/player/CraftingActions.java b/src/main/java/electrosphere/server/player/CraftingActions.java new file mode 100644 index 00000000..1e0a31e5 --- /dev/null +++ b/src/main/java/electrosphere/server/player/CraftingActions.java @@ -0,0 +1,216 @@ +package electrosphere.server.player; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import electrosphere.engine.Globals; +import electrosphere.entity.Entity; +import electrosphere.entity.state.inventory.InventoryUtils; +import electrosphere.entity.state.inventory.RelationalInventoryState; +import electrosphere.entity.state.inventory.UnrelationalInventoryState; +import electrosphere.entity.state.item.ServerChargeState; +import electrosphere.game.data.crafting.RecipeData; +import electrosphere.game.data.crafting.RecipeIngredientData; +import electrosphere.game.data.item.Item; +import electrosphere.game.data.item.ItemDataMap; + +/** + * Actions around crafting + */ +public class CraftingActions { + + /** + * Attempts to craft an item + * @param crafter The entity performing the crafting + * @param station The (optional) station for crafting + * @param recipe The recipe to craft + */ + public static void attemptCraft(Entity crafter, Entity station, RecipeData recipe){ + if(InventoryUtils.serverGetInventoryState(crafter) == null){ + return; + } + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(crafter); + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(crafter); + + //get data obj + ItemDataMap itemMap = Globals.gameConfigCurrent.getItemMap(); + + //get reagents + List reagentList = new LinkedList(); + boolean hasReagents = true; + Map ingredientTargetMap = new HashMap(); + Map ingredientAccretionMap = new HashMap(); + //find the reagents we're going to use to craft + for(RecipeIngredientData ingredient : recipe.getIngredients()){ + ingredientTargetMap.put(ingredient.getItemType(),ingredient.getCount()); + ingredientAccretionMap.put(ingredient.getItemType(),0); + int found = 0; + if(naturalInventory != null){ + for(Entity itemEnt : naturalInventory.getItems()){ + if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ + if(ServerChargeState.hasServerChargeState(itemEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); + found = found + serverChargeState.getCharges(); + } else { + found++; + } + reagentList.add(itemEnt); + } + if(found >= ingredient.getCount()){ + break; + } + } + } + if(toolbarInventory != null){ + for(Entity itemEnt : toolbarInventory.getItems()){ + if(itemEnt == null){ + continue; + } + if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ + if(ServerChargeState.hasServerChargeState(itemEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); + found = found + serverChargeState.getCharges(); + } else { + found++; + } + reagentList.add(itemEnt); + } + if(found >= ingredient.getCount()){ + break; + } + } + } + if(found < ingredient.getCount()){ + hasReagents = false; + break; + } + } + if(hasReagents){ + for(Entity reagentEnt : reagentList){ + + //determine how many we need to still remove + Item itemData = itemMap.getItem(reagentEnt); + int targetToRemove = ingredientTargetMap.get(itemData.getId()); + int alreadyFound = ingredientAccretionMap.get(itemData.getId()); + if(alreadyFound > targetToRemove){ + throw new Error("Removed too many ingredients! " + targetToRemove + " " + alreadyFound); + } else if(alreadyFound == targetToRemove){ + continue; + } + + + if(ServerChargeState.hasServerChargeState(reagentEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(reagentEnt); + int available = serverChargeState.getCharges(); + + if(available > targetToRemove - alreadyFound){ + int chargesToRemove = targetToRemove - alreadyFound; + serverChargeState.attemptAddCharges(-chargesToRemove); + //just remove charges + ingredientAccretionMap.put(itemData.getId(),alreadyFound + chargesToRemove); + } else { + //remove item entirely (consuming all charges) + if(naturalInventory != null){ + naturalInventory.removeItem(reagentEnt); + } + if(toolbarInventory != null){ + toolbarInventory.tryRemoveItem(reagentEnt); + } + InventoryUtils.serverDestroyInventoryItem(reagentEnt); + ingredientAccretionMap.put(itemData.getId(),alreadyFound + available); + } + } else { + if(naturalInventory != null){ + naturalInventory.removeItem(reagentEnt); + } + if(toolbarInventory != null){ + toolbarInventory.tryRemoveItem(reagentEnt); + } + InventoryUtils.serverDestroyInventoryItem(reagentEnt); + ingredientAccretionMap.put(itemData.getId(),alreadyFound + 1); + } + } + for(RecipeIngredientData product : recipe.getProducts()){ + Item productType = itemMap.getItem(product.getItemType()); + if(productType == null){ + throw new Error("Could not locate product definition! " + productType + " " + product.getItemType()); + } + InventoryUtils.serverCreateInventoryItem(crafter, product.getItemType(), product.getCount()); + } + } + } + + /** + * Checks if the item can be crafted + * @param crafter The entity performing the crafting + * @param workstation The workstation to perform the crafting at + * @param recipe The recipe to craft + * @return true if it can perform the recipe, false otherwise + */ + public static boolean canCraft(Entity crafter, Entity workstation, RecipeData recipe){ + if(InventoryUtils.serverGetInventoryState(crafter) == null){ + return false; + } + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(crafter); + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(crafter); + + //get data obj + ItemDataMap itemMap = Globals.gameConfigCurrent.getItemMap(); + + //get reagents + List reagentList = new LinkedList(); + boolean hasReagents = true; + Map ingredientTargetMap = new HashMap(); + Map ingredientAccretionMap = new HashMap(); + //find the reagents we're going to use to craft + for(RecipeIngredientData ingredient : recipe.getIngredients()){ + ingredientTargetMap.put(ingredient.getItemType(),ingredient.getCount()); + ingredientAccretionMap.put(ingredient.getItemType(),0); + int found = 0; + if(naturalInventory != null){ + for(Entity itemEnt : naturalInventory.getItems()){ + if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ + if(ServerChargeState.hasServerChargeState(itemEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); + found = found + serverChargeState.getCharges(); + } else { + found++; + } + reagentList.add(itemEnt); + } + if(found >= ingredient.getCount()){ + break; + } + } + } + if(toolbarInventory != null){ + for(Entity itemEnt : toolbarInventory.getItems()){ + if(itemEnt == null){ + continue; + } + if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ + if(ServerChargeState.hasServerChargeState(itemEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); + found = found + serverChargeState.getCharges(); + } else { + found++; + } + reagentList.add(itemEnt); + } + if(found >= ingredient.getCount()){ + break; + } + } + } + if(found < ingredient.getCount()){ + hasReagents = false; + break; + } + } + + return hasReagents; + } + +}