ai crafting
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-05-01 20:44:31 -04:00
parent 48622bb08f
commit f0ff713413
11 changed files with 345 additions and 169 deletions

View File

@ -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

View File

@ -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());

View File

@ -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<RecipeData> recipes, List<CommonEntityType> harvestTargets, List<FoliageType> trees){
public ItemSourcingData(String goalItem, List<RecipeData> recipes, List<CommonEntityType> harvestTargets, List<FoliageType> 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;
}
}

View File

@ -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);
}

View File

@ -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<InventoryMessag
Entity workshopEntity = EntityLookupUtils.getEntityById(message.getstationId());
RecipeData recipe = Globals.gameConfigCurrent.getRecipeMap().getType(message.getrecipeId());
if(target != null && recipe != null){
this.attemptCraft(target,workshopEntity,recipe);
CraftingActions.attemptCraft(target,workshopEntity,recipe);
// System.out.println(message.getentityId() + " " + message.getstationId() + " " + message.getrecipeId());
// InventoryUtils.serverGetInventoryState(target).addNetworkMessage(message);
}
@ -122,148 +110,4 @@ public class InventoryProtocol implements ServerProtocolTemplate<InventoryMessag
}
}
/**
* 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
*/
private 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<Entity> reagentList = new LinkedList<Entity>();
boolean hasReagents = true;
Map<String,Integer> ingredientTargetMap = new HashMap<String,Integer>();
Map<String,Integer> ingredientAccretionMap = new HashMap<String,Integer>();
//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);
}
}
}

View File

@ -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
*/

View File

@ -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;
}
}

View File

@ -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<Entity> nearbyEntities = NearbyEntityService.getNearbyEntities(blackboard);
List<String> 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;

View File

@ -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);
}
}

View File

@ -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)
)

View File

@ -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<Entity> reagentList = new LinkedList<Entity>();
boolean hasReagents = true;
Map<String,Integer> ingredientTargetMap = new HashMap<String,Integer>();
Map<String,Integer> ingredientAccretionMap = new HashMap<String,Integer>();
//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<Entity> reagentList = new LinkedList<Entity>();
boolean hasReagents = true;
Map<String,Integer> ingredientTargetMap = new HashMap<String,Integer>();
Map<String,Integer> ingredientAccretionMap = new HashMap<String,Integer>();
//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;
}
}