From 48622bb08f3d51cb2fa2fd7a632c364994ff240d Mon Sep 17 00:00:00 2001 From: austin Date: Thu, 1 May 2025 20:11:29 -0400 Subject: [PATCH] huge ai work --- assets/Data/entity/foliage/bushes.json | 3 +- assets/Data/entity/foliage/rocks.json | 1 + assets/Data/entity/items/materials/rocks.json | 3 +- assets/Data/entity/items/materials/wood.json | 3 +- assets/Data/game/recipes.json | 3 +- assets/Data/game/recipes/voxelrecipes.json | 23 +++ docs/src/progress/renderertodo.md | 6 +- .../entity/camera/CameraEntityUtils.java | 3 +- .../state/inventory/InventoryUtils.java | 17 +++ .../entity/state/life/ServerLifeTree.java | 6 +- .../groundmove/ServerGroundMovementTree.java | 7 +- .../types/terrain/BlockChunkEntity.java | 3 + .../entity/types/terrain/TerrainChunk.java | 1 + .../java/electrosphere/game/data/Config.java | 17 +++ .../game/data/common/CommonEntityTokens.java | 23 +++ .../electrosphere/game/data/item/Item.java | 11 +- .../game/data/item/ItemIdStrings.java | 13 ++ .../data/item/source/ItemSourcingData.java | 85 +++++++++++ .../data/item/source/ItemSourcingMap.java | 129 ++++++++++++++++ .../data/item/source/ItemSourcingTree.java | 138 ++++++++++++++++++ .../server/ai/blackboard/BlackboardKeys.java | 30 ++++ .../actions/interact/CollectItemNode.java | 8 +- .../nodes/actions/interact/HarvestNode.java | 37 ++++- .../actions/interact/PlaceBlockNode.java | 18 +++ .../ai/nodes/actions/move/FaceTargetNode.java | 37 +++-- .../nodes/checks/BlackboardKeyCheckNode.java | 33 +++++ .../inventory/InventoryContainsNode.java | 104 +++++++++++++ .../checks/inventory/SourcingTypeNode.java | 62 ++++++++ .../ai/nodes/checks/macro/HasShelter.java | 3 + .../checks/spatial/BeginStructureNode.java | 85 +++++++++++ ...geCheck.java => TargetRangeCheckNode.java} | 26 +++- .../ai/nodes/meta/DataTransferNode.java | 39 +++++ .../ai/nodes/plan/BuildStructureNode.java | 16 -- .../ai/nodes/plan/FindEntityTargetNode.java | 56 ------- .../ai/nodes/plan/SetBuildGoalNode.java | 35 ----- .../ai/nodes/plan/SolveSourcingTreeNode.java | 106 ++++++++++++++ .../nodes/plan/TargetEntityCategoryNode.java | 106 ++++++++++++++ .../ai/nodes/plan/TargetPositionNode.java | 27 +++- .../nodes/solvers/FindEntityTargetNode.java | 28 ++++ .../nodes/solvers/SolveBuildMaterialNode.java | 47 ++++++ .../ai/trees/creature/AcquireItemTree.java | 66 +++++++++ .../ai/trees/creature/MoveToTarget.java | 11 +- .../ai/trees/creature/melee/MeleeAITree.java | 15 +- .../safety/shelter/ConstructShelterTree.java | 5 + .../ai/trees/struct/BuildStructureTree.java | 93 ++++++++++++ .../server/ai/trees/test/BlockerAITree.java | 3 +- .../server/datacell/ServerWorldData.java | 4 +- .../serialization/ContentSerialization.java | 7 + .../electrosphere/server/macro/MacroData.java | 18 +-- .../server/macro/structure/Structure.java | 21 +++ .../macro/utils/StructurePlacementUtils.java | 24 +++ .../macro/utils/StructureRepairUtils.java | 49 +++++++ 52 files changed, 1543 insertions(+), 171 deletions(-) create mode 100644 assets/Data/game/recipes/voxelrecipes.json create mode 100644 src/main/java/electrosphere/game/data/common/CommonEntityTokens.java create mode 100644 src/main/java/electrosphere/game/data/item/ItemIdStrings.java create mode 100644 src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java create mode 100644 src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java create mode 100644 src/main/java/electrosphere/game/data/item/source/ItemSourcingTree.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/actions/interact/PlaceBlockNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/checks/BlackboardKeyCheckNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/checks/spatial/BeginStructureNode.java rename src/main/java/electrosphere/server/ai/nodes/checks/spatial/{TargetRangeCheck.java => TargetRangeCheckNode.java} (56%) create mode 100644 src/main/java/electrosphere/server/ai/nodes/meta/DataTransferNode.java delete mode 100644 src/main/java/electrosphere/server/ai/nodes/plan/BuildStructureNode.java delete mode 100644 src/main/java/electrosphere/server/ai/nodes/plan/FindEntityTargetNode.java delete mode 100644 src/main/java/electrosphere/server/ai/nodes/plan/SetBuildGoalNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/plan/TargetEntityCategoryNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/solvers/FindEntityTargetNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/solvers/SolveBuildMaterialNode.java create mode 100644 src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java create mode 100644 src/main/java/electrosphere/server/ai/trees/struct/BuildStructureTree.java create mode 100644 src/main/java/electrosphere/server/macro/utils/StructurePlacementUtils.java create mode 100644 src/main/java/electrosphere/server/macro/utils/StructureRepairUtils.java diff --git a/assets/Data/entity/foliage/bushes.json b/assets/Data/entity/foliage/bushes.json index c1763c54..2cecf440 100644 --- a/assets/Data/entity/foliage/bushes.json +++ b/assets/Data/entity/foliage/bushes.json @@ -3,7 +3,8 @@ { "id" : "bush4", "tokens" : [ - "FLAMMABLE" + "FLAMMABLE", + "HARVESTABLE" ], "hitboxes" : [ { diff --git a/assets/Data/entity/foliage/rocks.json b/assets/Data/entity/foliage/rocks.json index 13b396b4..0e09e8b8 100644 --- a/assets/Data/entity/foliage/rocks.json +++ b/assets/Data/entity/foliage/rocks.json @@ -3,6 +3,7 @@ { "id" : "rock_static", "tokens" : [ + "HARVESTABLE" ], "buttonInteraction" : { "onInteract" : "harvest", diff --git a/assets/Data/entity/items/materials/rocks.json b/assets/Data/entity/items/materials/rocks.json index 810f2de5..d6dbf89f 100644 --- a/assets/Data/entity/items/materials/rocks.json +++ b/assets/Data/entity/items/materials/rocks.json @@ -5,7 +5,8 @@ "maxStack" : 100, "tokens" : [ "GRAVITY", - "TARGETABLE" + "TARGETABLE", + "HARVESTABLE" ], "itemAudio": { "uiGrabAudio" : "Audio/ui/items/specific/Pick Up Stone A.wav", diff --git a/assets/Data/entity/items/materials/wood.json b/assets/Data/entity/items/materials/wood.json index bb927858..92d1ce69 100644 --- a/assets/Data/entity/items/materials/wood.json +++ b/assets/Data/entity/items/materials/wood.json @@ -5,7 +5,8 @@ "maxStack" : 100, "tokens" : [ "GRAVITY", - "TARGETABLE" + "TARGETABLE", + "HARVESTABLE" ], "itemAudio": { "uiGrabAudio" : "Audio/ui/items/specific/Pick Up Wood A.wav", diff --git a/assets/Data/game/recipes.json b/assets/Data/game/recipes.json index fba94ba4..ecd0979d 100644 --- a/assets/Data/game/recipes.json +++ b/assets/Data/game/recipes.json @@ -20,6 +20,7 @@ "files": [ "Data/game/recipes/weapons.json", "Data/game/recipes/tools.json", - "Data/game/recipes/fabs.json" + "Data/game/recipes/fabs.json", + "Data/game/recipes/voxelrecipes.json" ] } \ No newline at end of file diff --git a/assets/Data/game/recipes/voxelrecipes.json b/assets/Data/game/recipes/voxelrecipes.json new file mode 100644 index 00000000..04942375 --- /dev/null +++ b/assets/Data/game/recipes/voxelrecipes.json @@ -0,0 +1,23 @@ +{ + "recipes": [ + { + "displayName": "Refiend Wood", + "craftingTag" : "HAND", + "ingredients": [ + { + "itemType": "mat:Log", + "count": 1 + } + ], + "products": [ + { + "itemType": "block:refined_wood", + "count": 1 + } + ] + } + ], + "files": [ + + ] +} \ No newline at end of file diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 9731474a..e24c7f0e 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1607,7 +1607,7 @@ Items keep charge state UI renders charge state Item stacking -(04/20/2025) +(04/30/2025) Voxel placement improvements Smaller wall section First proper house~! @@ -1630,6 +1630,10 @@ DB characters store toolbar items Scaffolding for new macro-cognizating ai approach AI work +(05/01/2025) +Many new AI trees + - AI can seek out items + - AI can harvest entities diff --git a/src/main/java/electrosphere/client/entity/camera/CameraEntityUtils.java b/src/main/java/electrosphere/client/entity/camera/CameraEntityUtils.java index 9946514e..7903d97e 100644 --- a/src/main/java/electrosphere/client/entity/camera/CameraEntityUtils.java +++ b/src/main/java/electrosphere/client/entity/camera/CameraEntityUtils.java @@ -349,7 +349,8 @@ public class CameraEntityUtils { */ public static Vector3d getFacingVec(Quaterniond rotation){ //quaternion is multiplied by pi because we want to point away from the eye of the camera, NOT towards it - Matrix4d rotationMat = new Matrix4d().rotate(rotation); + Quaterniond quatd = new Quaterniond(rotation).normalize(); + Matrix4d rotationMat = new Matrix4d().rotate(quatd); Vector4d rotationVecRaw = SpatialMathUtils.getOriginVector4(); rotationVecRaw = rotationMat.transform(rotationVecRaw); if(rotationVecRaw.length() < 0.001){ diff --git a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java index eaa120c4..3d006ac5 100644 --- a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java +++ b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java @@ -747,4 +747,21 @@ public class InventoryUtils { return rVal; } + /** + * Checks if the enity has a given type of tool + * @param entity The entity + * @param toolType The type of tool + * @return true if it has the type of tool, false otherwise + */ + public static boolean serverHasTool(Entity entity, String toolType){ + List items = InventoryUtils.getAllInventoryItems(entity); + for(Entity itemEnt : items){ + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(itemEnt); + if(itemData.getTokens().contains(toolType)){ + return true; + } + } + return false; + } + } diff --git a/src/main/java/electrosphere/entity/state/life/ServerLifeTree.java b/src/main/java/electrosphere/entity/state/life/ServerLifeTree.java index ec16ef3d..6d1ea95d 100644 --- a/src/main/java/electrosphere/entity/state/life/ServerLifeTree.java +++ b/src/main/java/electrosphere/entity/state/life/ServerLifeTree.java @@ -124,8 +124,10 @@ public class ServerLifeTree implements BehaviorTree { * Kills the entity */ public void kill(){ - lifeCurrent = 0; - this.setState(LifeStateEnum.DYING); + if(this.getState() == LifeStateEnum.ALIVE){ + lifeCurrent = 0; + this.setState(LifeStateEnum.DYING); + } } /** diff --git a/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java b/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java index 228076e2..4575f8e5 100644 --- a/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java @@ -31,6 +31,7 @@ import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums; import electrosphere.net.synchronization.enums.FieldIdEnums; import electrosphere.renderer.anim.Animation; import electrosphere.script.utils.AccessTransforms; +import electrosphere.server.ai.AI; import electrosphere.server.datacell.utils.DataCellSearchUtils; import electrosphere.server.utils.ServerScriptUtils; import electrosphere.util.math.SpatialMathUtils; @@ -175,8 +176,10 @@ public class ServerGroundMovementTree implements BehaviorTree { Vector3d position = EntityUtils.getPosition(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); if(ServerPlayerViewDirTree.hasTree(parent)){ - ServerPlayerViewDirTree serverViewTree =ServerPlayerViewDirTree.getTree(parent); - facingVector = CameraEntityUtils.getFacingVec(serverViewTree.getYaw(), serverViewTree.getPitch()); + if(AI.getAI(parent) == null || !AI.getAI(parent).isApplyToPlayer()){ + ServerPlayerViewDirTree serverViewTree =ServerPlayerViewDirTree.getTree(parent); + facingVector = CameraEntityUtils.getFacingVec(serverViewTree.getYaw(), serverViewTree.getPitch()); + } } DBody body = PhysicsEntityUtils.getDBody(parent); DVector3C linearVelocity = body.getLinearVel(); diff --git a/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java b/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java index d50eb995..2c032f82 100644 --- a/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java +++ b/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java @@ -19,7 +19,9 @@ import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; +import electrosphere.entity.types.EntityTypes.EntityType; import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.entity.types.common.CommonEntityUtils; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.meshgen.BlockMeshgen; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; @@ -119,6 +121,7 @@ public class BlockChunkEntity { Quaterniond entityRot = EntityUtils.getRotation(entity); PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), entityPos, entityRot, terrainBody); entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); + CommonEntityUtils.setEntityType(entity, EntityType.ENGINE); } } diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index 89824e10..be0af912 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -136,6 +136,7 @@ public class TerrainChunk { Quaterniond entityRot = EntityUtils.getRotation(entity); PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), entityPos, entityRot, terrainBody); entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); + CommonEntityUtils.setEntityType(entity, EntityType.ENGINE); } // ServerEntityUtils.initiallyPositionEntity(realm, rVal, position); // physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true); diff --git a/src/main/java/electrosphere/game/data/Config.java b/src/main/java/electrosphere/game/data/Config.java index 6d2f3a3b..87d006c7 100644 --- a/src/main/java/electrosphere/game/data/Config.java +++ b/src/main/java/electrosphere/game/data/Config.java @@ -18,6 +18,7 @@ import electrosphere.game.data.foliage.type.FoliageType; import electrosphere.game.data.foliage.type.FoliageTypeLoader; import electrosphere.game.data.foliage.type.model.FoliageTypeMap; import electrosphere.game.data.item.ItemDataMap; +import electrosphere.game.data.item.source.ItemSourcingMap; import electrosphere.game.data.projectile.ProjectileTypeHolder; import electrosphere.game.data.struct.StructureDataLoader; import electrosphere.game.data.tutorial.HintDefinition; @@ -95,6 +96,11 @@ public class Config { * The structure data */ StructureDataLoader structureData; + + /** + * The item sourcing map for items + */ + ItemSourcingMap itemSourcingMap; /** * Loads the default data @@ -124,6 +130,9 @@ public class Config { ItemDataMap.loadSpawnItems(config.itemMap, config.objectTypeLoader); ItemDataMap.generateBlockItems(config.itemMap, config.blockData); + //construct the sourcing map + config.itemSourcingMap = ItemSourcingMap.parse(config); + //validate ConfigValidator.valdiate(config); @@ -375,5 +384,13 @@ public class Config { public StructureDataLoader getStructureData(){ return this.structureData; } + + /** + * Gets the item sourcing map of the config + * @return The item sourcing map + */ + public ItemSourcingMap getItemSourcingMap(){ + return this.itemSourcingMap; + } } diff --git a/src/main/java/electrosphere/game/data/common/CommonEntityTokens.java b/src/main/java/electrosphere/game/data/common/CommonEntityTokens.java new file mode 100644 index 00000000..58d1c79b --- /dev/null +++ b/src/main/java/electrosphere/game/data/common/CommonEntityTokens.java @@ -0,0 +1,23 @@ +package electrosphere.game.data.common; + +/** + * All common entity tokens + */ +public class CommonEntityTokens { + + /** + * This entity is a tree foliage type + */ + public static final String TOKEN_TREE = "TREE"; + + /** + * An axe + */ + public static final String TOKEN_AXE = "AXE"; + + /** + * Flags this object as harvestable + */ + public static final String TOKEN_HARVESTABLE = "HARVESTABLE"; + +} diff --git a/src/main/java/electrosphere/game/data/item/Item.java b/src/main/java/electrosphere/game/data/item/Item.java index 856fd3d0..37f0b7e5 100644 --- a/src/main/java/electrosphere/game/data/item/Item.java +++ b/src/main/java/electrosphere/game/data/item/Item.java @@ -124,6 +124,15 @@ public class Item extends CommonEntityType { return rVal; } + /** + * Gets the id of the item type for a given block type + * @param blockType The block type + * @return The id of the corresponding item data + */ + public static String getBlockTypeId(BlockType blockType){ + return "block:" + blockType.getName(); + } + /** * Creates item data from a block type * @param description The block type @@ -131,7 +140,7 @@ public class Item extends CommonEntityType { */ public static Item createBlockItem(BlockType blockType){ Item rVal = new Item(); - rVal.setId("block:" + blockType.getName()); + rVal.setId(Item.getBlockTypeId(blockType)); if(blockType.getTexture() != null){ diff --git a/src/main/java/electrosphere/game/data/item/ItemIdStrings.java b/src/main/java/electrosphere/game/data/item/ItemIdStrings.java new file mode 100644 index 00000000..7531293f --- /dev/null +++ b/src/main/java/electrosphere/game/data/item/ItemIdStrings.java @@ -0,0 +1,13 @@ +package electrosphere.game.data.item; + +/** + * Hardcoded item ids for items + */ +public class ItemIdStrings { + + /** + * The basic stone axe + */ + public static final String ITEM_STONE_AXE = "Stone Axe"; + +} diff --git a/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java b/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java new file mode 100644 index 00000000..d61b1704 --- /dev/null +++ b/src/main/java/electrosphere/game/data/item/source/ItemSourcingData.java @@ -0,0 +1,85 @@ +package electrosphere.game.data.item.source; + +import java.util.List; + +import electrosphere.game.data.common.CommonEntityType; +import electrosphere.game.data.crafting.RecipeData; +import electrosphere.game.data.foliage.type.FoliageType; + +/** + * Data that stores how an item can be sources + */ +public class ItemSourcingData { + + /** + * Types of sources for items + */ + public static enum SourcingType { + /** + * Craft a recipe + */ + RECIPE, + /** + * Harvest from an item + */ + HARVEST, + /** + * Fell a tree + */ + TREE, + } + + /** + * The list of recipes that create this item + */ + List recipes; + + /** + * The list of harvesting targets that can drop this item + */ + List harvestTargets; + + /** + * The list of trees that can drop this item + */ + List trees; + + /** + * Constructor + * @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){ + this.recipes = recipes; + this.harvestTargets = harvestTargets; + this.trees = trees; + } + + /** + * Gets the list of recipes that can produce this item + * @return The list of recipes + */ + public List getRecipes() { + return recipes; + } + + /** + * Gets the list of harvest targets that can drop this item + * @return The list of harvest targets + */ + public List getHarvestTargets() { + return harvestTargets; + } + + /** + * Gets the list of trees that can drop this item + * @return The list of trees + */ + public List getTrees() { + return trees; + } + + + +} diff --git a/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java b/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java new file mode 100644 index 00000000..a26af031 --- /dev/null +++ b/src/main/java/electrosphere/game/data/item/source/ItemSourcingMap.java @@ -0,0 +1,129 @@ +package electrosphere.game.data.item.source; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import electrosphere.game.data.Config; +import electrosphere.game.data.common.CommonEntityTokens; +import electrosphere.game.data.common.CommonEntityType; +import electrosphere.game.data.common.life.loot.LootPool; +import electrosphere.game.data.common.life.loot.LootTicket; +import electrosphere.game.data.crafting.RecipeData; +import electrosphere.game.data.crafting.RecipeDataMap; +import electrosphere.game.data.crafting.RecipeIngredientData; +import electrosphere.game.data.foliage.type.FoliageType; +import electrosphere.game.data.foliage.type.FoliageTypeLoader; +import electrosphere.game.data.item.Item; +import electrosphere.game.data.item.ItemDataMap; + +/** + * Map of items to the methods to source them + */ +public class ItemSourcingMap { + + /** + * Map of item id -> sourcing data + */ + private Map itemSourcingDataMap = new HashMap(); + + /** + * Parses a sourcing map from a given config + * @param config The config + * @return The sourcing map + */ + public static ItemSourcingMap parse(Config config){ + ItemSourcingMap sourcingMap = new ItemSourcingMap(); + + RecipeDataMap recipeMap = config.getRecipeMap(); + ItemDataMap itemMap = config.getItemMap(); + FoliageTypeLoader foliageMap = config.getFoliageMap(); + + //structures that store sources + List recipes; + List harvestTargets; + List trees; + + //iterate through each item and find where they can be sources + for(Item item : itemMap.getTypes()){ + //construct new lists for each item type + recipes = new LinkedList(); + harvestTargets = new LinkedList(); + trees = new LinkedList(); + //check if any recipes can create this item + for(RecipeData recipe : recipeMap.getTypes()){ + for(RecipeIngredientData product : recipe.getProducts()){ + if(product.getItemType().equals(item.getId())){ + recipes.add(recipe); + break; + } + } + } + + //check if any common objects can from this item as loot + for(CommonEntityType foliageEnt : foliageMap.getTypes()){ + if(foliageEnt.getTokens() == null){ + continue; + } + if(!foliageEnt.getTokens().contains(CommonEntityTokens.TOKEN_HARVESTABLE)){ + continue; + } + if(foliageEnt.getHealthSystem() != null){ + if(foliageEnt.getHealthSystem().getLootPool() != null){ + LootPool lootPool = foliageEnt.getHealthSystem().getLootPool(); + for(LootTicket ticket : lootPool.getTickets()){ + if(ticket.getItemId().equals(item.getId())){ + harvestTargets.add(foliageEnt); + break; + } + } + } + } + } + + //check if any trees can drop the item + for(FoliageType foliage : foliageMap.getTypes()){ + if(foliage.getTokens() != null && foliage.getTokens().contains(CommonEntityTokens.TOKEN_TREE)){ + if(foliage.getHealthSystem() != null){ + if(foliage.getHealthSystem().getLootPool() != null){ + LootPool lootPool = foliage.getHealthSystem().getLootPool(); + for(LootTicket ticket : lootPool.getTickets()){ + if(ticket.getItemId().equals(item.getId())){ + trees.add(foliage); + break; + } + } + } + } + } + } + + //store the sourcing data + ItemSourcingData sourcingData = new ItemSourcingData(recipes, harvestTargets, trees); + sourcingMap.itemSourcingDataMap.put(item.getId(),sourcingData); + } + + + return sourcingMap; + } + + /** + * Gets the sourcing data for a given item type + * @param itemId The item type + * @return The sourcing data for that type of item + */ + public ItemSourcingData getSourcingData(String itemId){ + return itemSourcingDataMap.get(itemId); + } + + /** + * Gets the sourcing data for a given item type + * @param item The item data + * @return The sourcing data for that type of item + */ + public ItemSourcingData getSourcingData(Item item){ + return itemSourcingDataMap.get(item.getId()); + } + +} diff --git a/src/main/java/electrosphere/game/data/item/source/ItemSourcingTree.java b/src/main/java/electrosphere/game/data/item/source/ItemSourcingTree.java new file mode 100644 index 00000000..bd4d50c6 --- /dev/null +++ b/src/main/java/electrosphere/game/data/item/source/ItemSourcingTree.java @@ -0,0 +1,138 @@ +package electrosphere.game.data.item.source; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import electrosphere.engine.Globals; +import electrosphere.entity.Entity; +import electrosphere.entity.state.inventory.InventoryUtils; +import electrosphere.entity.types.item.ItemUtils; +import electrosphere.game.data.common.CommonEntityTokens; +import electrosphere.game.data.crafting.RecipeData; +import electrosphere.game.data.crafting.RecipeIngredientData; +import electrosphere.game.data.item.ItemIdStrings; + +/** + * A tree of dependencies to acquire a final item + */ +public class ItemSourcingTree { + + /** + * The root item to search for + */ + String rootItemId; + + /** + * The map of item id -> specific + */ + Map itemSourceMap = new HashMap(); + + /** + * Creates an item sourcing tree + * @param itemType The type of item to source + * @return The tree of dependencies to get the item + */ + public static ItemSourcingTree create(String itemType){ + ItemSourcingTree rVal = new ItemSourcingTree(); + rVal.rootItemId = itemType; + rVal.evaluate(); + return rVal; + } + + /** + * Evaluates what items are still depended upon + */ + public void evaluate(){ + //all items that haven't had sources solved for yet + List unsolvedItems = new LinkedList(); + unsolvedItems.add(this.rootItemId); + + while(unsolvedItems.size() > 0){ + String currentId = unsolvedItems.remove(0); + ItemSourcingData sourcingData = Globals.gameConfigCurrent.getItemSourcingMap().getSourcingData(currentId); + if(sourcingData.recipes.size() > 0){ + for(RecipeData recipeData : sourcingData.recipes){ + for(RecipeIngredientData reagent : recipeData.getIngredients()){ + if(!unsolvedItems.contains(reagent.getItemType()) && !itemSourceMap.containsKey(reagent.getItemType())){ + unsolvedItems.add(reagent.getItemType()); + } + } + } + } + if(sourcingData.trees.size() > 0){ + unsolvedItems.add(ItemIdStrings.ITEM_STONE_AXE); + } + this.itemSourceMap.put(currentId,sourcingData); + } + } + + /** + * Gets the sourcing data for the current dependency + * @param entity The entity to check + * @return The sourcing data for the current dependency + */ + public ItemSourcingData getCurrentDependency(Entity entity){ + List items = InventoryUtils.getAllInventoryItems(entity); + List itemIds = items.stream().map((Entity item) -> {return ItemUtils.getType(item);}).collect(Collectors.toList()); + if(itemIds.contains(this.rootItemId)){ + return null; + } + String currentRootId = this.rootItemId; + while(currentRootId != null){ + ItemSourcingData sourcingData = this.itemSourceMap.get(currentRootId); + if(sourcingData == null){ + throw new Error("Failed to find sourcing data for " + currentRootId); + } + if(sourcingData.harvestTargets.size() > 0){ + return sourcingData; + } + if(sourcingData.trees.size() > 0){ + //if we don't have an axe in inventory, consider it a dependency + if(!InventoryUtils.serverHasTool(entity, CommonEntityTokens.TOKEN_AXE)){ + currentRootId = ItemIdStrings.ITEM_STONE_AXE; + continue; + } + //we have an axe, return this sourcing data + return sourcingData; + } + + currentRootId = null; + if(sourcingData.recipes.size() > 0){ + boolean foundAllIngredients = true; + RecipeData craftableRecipe = null; + for(RecipeData recipeData : sourcingData.recipes){ + //check if we have all the ingredients to craft this item + foundAllIngredients = true; + for(RecipeIngredientData ingredient : recipeData.getIngredients()){ + if(!itemIds.contains(ingredient.getItemType())){ + //ingredient is not in inventory + foundAllIngredients = false; + currentRootId = ingredient.getItemType(); + break; + } + } + if(foundAllIngredients){ + craftableRecipe = recipeData; + break; + } + } + if(craftableRecipe != null){ + return sourcingData; + } + } + } + return null; + } + + /** + * Gets the item id of the root item + * @return The id + */ + public String getRootItem(){ + return this.rootItemId; + } + +} diff --git a/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java b/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java index ecba3ccd..d4ad5323 100644 --- a/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java +++ b/src/main/java/electrosphere/server/ai/blackboard/BlackboardKeys.java @@ -25,4 +25,34 @@ public class BlackboardKeys { */ public static final String MOVE_TO_TARGET = "moveToTarget"; + /** + * The structure that is being targeted + */ + public static final String STRUCTURE_TARGET = "structureTarget"; + + /** + * The material currently needed for building the targeted structure + */ + public static final String BUILDING_MATERIAL_CURRENT = "buildingMaterialCurrent"; + + /** + * The type of item to scan the inventory for + */ + public static final String INVENTORY_CHECK_TYPE = "inventoryCheckType"; + + /** + * Tree that stores the item sourcing + */ + public static final String ITEM_SOURCING_TREE = "itemSourcingTree"; + + /** + * Sourcing data for the item that is currently being sought after + */ + public static final String ITEM_SOURCING_DATA = "itemSourcingData"; + + /** + * The type of entity to try to harvest + */ + public static final String HARVEST_TARGET_TYPE = "harvestTargetType"; + } diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/interact/CollectItemNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/interact/CollectItemNode.java index 09d7c3c6..0f31baaf 100644 --- a/src/main/java/electrosphere/server/ai/nodes/actions/interact/CollectItemNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/actions/interact/CollectItemNode.java @@ -8,7 +8,7 @@ import electrosphere.entity.EntityUtils; import electrosphere.entity.state.inventory.InventoryUtils; import electrosphere.server.ai.blackboard.Blackboard; import electrosphere.server.ai.nodes.AITreeNode; -import electrosphere.server.ai.nodes.plan.FindEntityTargetNode; +import electrosphere.server.ai.nodes.plan.TargetEntityCategoryNode; /** * Tries to collect an item @@ -17,17 +17,17 @@ public class CollectItemNode implements AITreeNode { @Override public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ - if(!FindEntityTargetNode.hasTarget(blackboard)){ + if(!TargetEntityCategoryNode.hasTarget(blackboard)){ return AITreeNodeResult.FAILURE; } - Entity target = FindEntityTargetNode.getTarget(blackboard); + Entity target = TargetEntityCategoryNode.getTarget(blackboard); Vector3d parentPos = EntityUtils.getPosition(entity); Vector3d targetPos = EntityUtils.getPosition(target); if(parentPos.distance(targetPos) > CollisionEngine.DEFAULT_INTERACT_DISTANCE){ return AITreeNodeResult.FAILURE; } InventoryUtils.serverAttemptStoreItemTransform(entity, target); - FindEntityTargetNode.setTarget(blackboard, null); + TargetEntityCategoryNode.setTarget(blackboard, null); return AITreeNodeResult.SUCCESS; } diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/interact/HarvestNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/interact/HarvestNode.java index 8ef8cd26..7d6fb2ba 100644 --- a/src/main/java/electrosphere/server/ai/nodes/actions/interact/HarvestNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/actions/interact/HarvestNode.java @@ -6,8 +6,9 @@ import electrosphere.collision.CollisionEngine; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; import electrosphere.server.ai.nodes.AITreeNode; -import electrosphere.server.ai.nodes.plan.FindEntityTargetNode; +import electrosphere.server.ai.nodes.plan.TargetEntityCategoryNode; import electrosphere.server.player.PlayerActions; /** @@ -17,17 +18,45 @@ public class HarvestNode implements AITreeNode { @Override public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ - if(!FindEntityTargetNode.hasTarget(blackboard)){ + if(!TargetEntityCategoryNode.hasTarget(blackboard)){ return AITreeNodeResult.FAILURE; } - Entity target = FindEntityTargetNode.getTarget(blackboard); + Entity target = TargetEntityCategoryNode.getTarget(blackboard); Vector3d parentPos = EntityUtils.getPosition(entity); Vector3d targetPos = EntityUtils.getPosition(target); if(parentPos.distance(targetPos) > CollisionEngine.DEFAULT_INTERACT_DISTANCE){ return AITreeNodeResult.FAILURE; } PlayerActions.harvest(entity, target); - FindEntityTargetNode.setTarget(blackboard, null); + TargetEntityCategoryNode.setTarget(blackboard, null); return AITreeNodeResult.SUCCESS; } + + /** + * sets the type of entity to try to harvest + * @param blackboard The blackboard + * @param type The type of entity to try to harvest + */ + public static void setHarvestTargetType(Blackboard blackboard, String type){ + blackboard.put(BlackboardKeys.HARVEST_TARGET_TYPE, type); + } + + /** + * checks if this blackboard has a type of entity it wants to try to harvest + * @param blackboard The blackboard + * @return true if the type is defined, false otherwise + */ + public static boolean hasHarvestTargetType(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.HARVEST_TARGET_TYPE); + } + + /** + * Gets the type of entity to try to harvest + * @param blackboard The blackboard + * @return The type of entity to try to harvest + */ + public static String getHarvestTargetType(Blackboard blackboard){ + return (String)blackboard.get(BlackboardKeys.HARVEST_TARGET_TYPE); + } + } diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/interact/PlaceBlockNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/interact/PlaceBlockNode.java new file mode 100644 index 00000000..2a0510db --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/actions/interact/PlaceBlockNode.java @@ -0,0 +1,18 @@ +package electrosphere.server.ai.nodes.actions.interact; + +import electrosphere.entity.Entity; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Places a block + */ +public class PlaceBlockNode implements AITreeNode { + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'evaluate'"); + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/move/FaceTargetNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/move/FaceTargetNode.java index 4bd19c0f..f2b17be1 100644 --- a/src/main/java/electrosphere/server/ai/nodes/actions/move/FaceTargetNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/actions/move/FaceTargetNode.java @@ -9,7 +9,6 @@ import electrosphere.entity.EntityUtils; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.server.ai.blackboard.Blackboard; import electrosphere.server.ai.nodes.AITreeNode; -import electrosphere.server.ai.nodes.plan.TargetPositionNode; import electrosphere.util.math.SpatialMathUtils; /** @@ -17,17 +16,37 @@ import electrosphere.util.math.SpatialMathUtils; */ public class FaceTargetNode implements AITreeNode { + /** + * The key to lookup the target under + */ + String targetKey; + + /** + * Constructor + * @param targetKey The key to lookup the target under + */ + public FaceTargetNode(String targetKey){ + this.targetKey = targetKey; + } + @Override public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { - if(TargetPositionNode.hasMoveToTarget(blackboard)){ - Vector3d parentPos = EntityUtils.getPosition(entity); - Vector3d targetPos = TargetPositionNode.getMoveToTarget(blackboard); - Quaterniond rotation = SpatialMathUtils.calculateRotationFromPointToPoint(parentPos, targetPos); - EntityUtils.getRotation(entity).set(rotation); - CreatureUtils.setFacingVector(entity, CameraEntityUtils.getFacingVec(rotation)); - return AITreeNodeResult.SUCCESS; + Object targetRaw = blackboard.get(this.targetKey); + Vector3d targetPos = null; + if(targetRaw == null){ + throw new Error("Target undefined!"); } - return AITreeNodeResult.FAILURE; + if(targetRaw instanceof Vector3d){ + targetPos = (Vector3d)targetRaw; + } + if(targetRaw instanceof Entity){ + targetPos = EntityUtils.getPosition((Entity)targetRaw); + } + Vector3d parentPos = EntityUtils.getPosition(entity); + Quaterniond rotation = SpatialMathUtils.calculateRotationFromPointToPoint(parentPos, targetPos); + EntityUtils.getRotation(entity).set(rotation); + CreatureUtils.setFacingVector(entity, CameraEntityUtils.getFacingVec(rotation)); + return AITreeNodeResult.SUCCESS; } } diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/BlackboardKeyCheckNode.java b/src/main/java/electrosphere/server/ai/nodes/checks/BlackboardKeyCheckNode.java new file mode 100644 index 00000000..5bd5201b --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/checks/BlackboardKeyCheckNode.java @@ -0,0 +1,33 @@ +package electrosphere.server.ai.nodes.checks; + +import electrosphere.entity.Entity; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Checks if a blackboard key exists + */ +public class BlackboardKeyCheckNode implements AITreeNode { + + /** + * The key to check for + */ + String key; + + /** + * Constructor + * @param key The key to check for + */ + public BlackboardKeyCheckNode(String key){ + this.key = key; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + if(blackboard.has(this.key)){ + return AITreeNodeResult.SUCCESS; + } + return AITreeNodeResult.FAILURE; + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java new file mode 100644 index 00000000..afb21203 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java @@ -0,0 +1,104 @@ +package electrosphere.server.ai.nodes.checks.inventory; + +import java.util.List; +import java.util.stream.Collectors; + +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.types.common.CommonEntityUtils; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Checks if any of the inventories on a given entity contain the target item type + */ +public class InventoryContainsNode implements AITreeNode { + + /** + * The key to look for the item type in + */ + String key = BlackboardKeys.INVENTORY_CHECK_TYPE; + + /** + * Constructor + * @param key The key to lookup the entity type under + */ + public InventoryContainsNode(String key){ + this.key = key; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + + //error checking + if(!InventoryContainsNode.hasInventoryCheckType(blackboard)){ + return AITreeNodeResult.FAILURE; + } + + //key isn't defined + if(!blackboard.has(key)){ + return AITreeNodeResult.FAILURE; + } + + //type to look for + String type = (String)blackboard.get(this.key); + + //check equip inventory if it exists + if(InventoryUtils.hasEquipInventory(entity)){ + RelationalInventoryState equipInventory = InventoryUtils.getEquipInventory(entity); + if(equipInventory.getSlots().contains(type)){ + return AITreeNodeResult.SUCCESS; + } + } + + //check natural inventory if it exists + if(InventoryUtils.hasNaturalInventory(entity)){ + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(entity); + List itemIds = naturalInventory.getItems().stream().map((Entity itemEnt) -> {return CommonEntityUtils.getEntitySubtype(entity);}).collect(Collectors.toList()); + if(itemIds.contains(type)){ + return AITreeNodeResult.SUCCESS; + } + } + + //check toolbar inventory if it exists + if(InventoryUtils.hasToolbarInventory(entity)){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(entity); + if(toolbarInventory.getSlots().contains(type)){ + return AITreeNodeResult.SUCCESS; + } + } + + return AITreeNodeResult.FAILURE; + } + + /** + * Sets the type of item to check for + * @param blackboard The blackboard + * @param type The type of item to check for + */ + public static void setInventoryCheckType(Blackboard blackboard, String type){ + blackboard.put(BlackboardKeys.INVENTORY_CHECK_TYPE, type); + } + + /** + * Checks if this has an item type to search for + * @param blackboard The blackboard + * @return true if there is an item type to check for, false otherwise + */ + public static boolean hasInventoryCheckType(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.INVENTORY_CHECK_TYPE); + } + + /** + * Gets the item type to check for + * @param blackboard The blackboard + * @return The item type to check for if it exists, null otherwise + */ + public static String getInventoryCheckType(Blackboard blackboard){ + return (String)blackboard.get(BlackboardKeys.INVENTORY_CHECK_TYPE); + } + +} 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 new file mode 100644 index 00000000..1c511b43 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/SourcingTypeNode.java @@ -0,0 +1,62 @@ +package electrosphere.server.ai.nodes.checks.inventory; + +import electrosphere.entity.Entity; +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; + +/** + * Checks if the supplied type of sourcing is the path for the current target item to acquire + */ +public class SourcingTypeNode implements AITreeNode { + + /** + * The type of sourcing + */ + SourcingType sourcingType; + + /** + * Constructor + * @param sourcingType The type of sourcing to check + */ + public SourcingTypeNode(SourcingType sourcingType){ + this.sourcingType = sourcingType; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + if(!SolveSourcingTreeNode.hasItemSourcingData(blackboard)){ + return AITreeNodeResult.FAILURE; + } + + ItemSourcingData sourcingData = SolveSourcingTreeNode.getItemSourcingData(blackboard); + if(sourcingData == null){ + throw new Error("Sourcing data is null!"); + } + + //succeed based on the type of sourcing that this node is set for + switch(this.sourcingType){ + case RECIPE: { + if(sourcingData.getRecipes().size() > 0){ + return AITreeNodeResult.SUCCESS; + } + } break; + case HARVEST: { + if(sourcingData.getHarvestTargets().size() > 0){ + return AITreeNodeResult.SUCCESS; + } + } break; + case TREE: { + if(sourcingData.getTrees().size() > 0){ + return AITreeNodeResult.SUCCESS; + } + } break; + } + + + return AITreeNodeResult.FAILURE; + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/macro/HasShelter.java b/src/main/java/electrosphere/server/ai/nodes/checks/macro/HasShelter.java index a37770cd..68c4523e 100644 --- a/src/main/java/electrosphere/server/ai/nodes/checks/macro/HasShelter.java +++ b/src/main/java/electrosphere/server/ai/nodes/checks/macro/HasShelter.java @@ -22,6 +22,9 @@ public class HasShelter implements AITreeNode { MacroData macroData = entityRealm.getServerContentManager().getMacroData(); ServerCharacterData serverCharacterData = ServerCharacterData.getServerCharacterData(entity); Character character = macroData.getCharacter(serverCharacterData.getCharacterId()); + if(character == null){ + throw new Error("Character is null"); + } Structure shelter = CharacterUtils.getShelter(character); if(shelter == null){ return AITreeNodeResult.FAILURE; diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/spatial/BeginStructureNode.java b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/BeginStructureNode.java new file mode 100644 index 00000000..e74f57db --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/BeginStructureNode.java @@ -0,0 +1,85 @@ +package electrosphere.server.ai.nodes.checks.spatial; + +import org.joml.Vector3d; + +import electrosphere.engine.Globals; +import electrosphere.entity.Entity; +import electrosphere.entity.EntityUtils; +import electrosphere.game.data.block.BlockFab; +import electrosphere.game.data.struct.StructureData; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.datacell.Realm; +import electrosphere.server.macro.MacroData; +import electrosphere.server.macro.structure.Structure; +import electrosphere.server.macro.utils.StructurePlacementUtils; +import electrosphere.util.FileUtils; + +/** + * Tries to begin building a structure + */ +public class BeginStructureNode implements AITreeNode { + + /** + * The data for the structure to place + */ + StructureData structureData; + + /** + * Constructor + * @param structureData The type of structure to place + */ + public BeginStructureNode(StructureData structureData){ + this.structureData = structureData; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + if(!BeginStructureNode.hasStructureTarget(blackboard)){ + //requisite data + Realm realm = Globals.realmManager.getEntityRealm(entity); + MacroData macroData = realm.getServerContentManager().getMacroData(); + Vector3d position = EntityUtils.getPosition(entity); + + //solve where to place + Vector3d placementPos = StructurePlacementUtils.getPlacementPosition(macroData, structureData, position); + + //add to macro data + Structure struct = Structure.createStructure(structureData, placementPos); + struct.setRepairable(true); + struct.setFab(BlockFab.read(FileUtils.getAssetFile(struct.getFabPath()))); + // macroData.getStructures().add(struct); + + BeginStructureNode.setStructureTarget(blackboard, struct); + } + return AITreeNodeResult.SUCCESS; + } + + /** + * Sets the structure target for the entity + * @param blackboard The blackboard + * @param structure The structure to target + */ + public static void setStructureTarget(Blackboard blackboard, Structure structure){ + blackboard.put(BlackboardKeys.STRUCTURE_TARGET, structure); + } + + /** + * Checks if the blackboard has a structure target + * @param blackboard The blackboard + */ + public static boolean hasStructureTarget(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.STRUCTURE_TARGET); + } + + /** + * Gets the structure target in the blackboard + * @param blackboard The blackboard + * @return The structure if it exists, null otherwise + */ + public static Structure getStructureTarget(Blackboard blackboard){ + return (Structure)blackboard.get(BlackboardKeys.STRUCTURE_TARGET); + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheck.java b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java similarity index 56% rename from src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheck.java rename to src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java index 5d149d35..c386ab2b 100644 --- a/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheck.java +++ b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java @@ -6,33 +6,45 @@ import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.server.ai.blackboard.Blackboard; import electrosphere.server.ai.nodes.AITreeNode; -import electrosphere.server.ai.nodes.plan.FindEntityTargetNode; /** * Checks if the target is inside a given range of the entity */ -public class TargetRangeCheck implements AITreeNode { +public class TargetRangeCheckNode implements AITreeNode { /** * The distance to succeed within */ double dist; + /** + * The key to lookup the target under + */ + String targetKey; + /** * Constructor * @param dist The distance outside of which the node will fail + * @param targetKey The key to lookup the target under */ - public TargetRangeCheck(double dist){ + public TargetRangeCheckNode(double dist, String targetKey){ this.dist = dist; + this.targetKey = targetKey; } @Override public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { - if(!FindEntityTargetNode.hasTarget(blackboard)){ - return AITreeNodeResult.FAILURE; + Object targetRaw = blackboard.get(this.targetKey); + Vector3d targetPos = null; + if(targetRaw == null){ + throw new Error("Target undefined!"); + } + if(targetRaw instanceof Vector3d){ + targetPos = (Vector3d)targetRaw; + } + if(targetRaw instanceof Entity){ + targetPos = EntityUtils.getPosition((Entity)targetRaw); } - Entity target = FindEntityTargetNode.getTarget(blackboard); - Vector3d targetPos = EntityUtils.getPosition(target); Vector3d entPos = EntityUtils.getPosition(entity); if(targetPos.distance(entPos) < this.dist){ diff --git a/src/main/java/electrosphere/server/ai/nodes/meta/DataTransferNode.java b/src/main/java/electrosphere/server/ai/nodes/meta/DataTransferNode.java new file mode 100644 index 00000000..716f0829 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/meta/DataTransferNode.java @@ -0,0 +1,39 @@ +package electrosphere.server.ai.nodes.meta; + +import electrosphere.entity.Entity; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Transfers data from one blackboard key to another + */ +public class DataTransferNode implements AITreeNode { + + /** + * The key to pull data from + */ + String sourceKey; + + /** + * The key to push data into + */ + String destinationKey; + + /** + * Constructor + * @param sourceKey The key to pull data from + * @param destinationKey The key to push data into + */ + public DataTransferNode(String sourceKey, String destinationKey){ + this.sourceKey = sourceKey; + this.destinationKey = destinationKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + Object data = blackboard.get(this.sourceKey); + blackboard.put(this.destinationKey, data); + return AITreeNodeResult.SUCCESS; + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/BuildStructureNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/BuildStructureNode.java deleted file mode 100644 index f1ccb406..00000000 --- a/src/main/java/electrosphere/server/ai/nodes/plan/BuildStructureNode.java +++ /dev/null @@ -1,16 +0,0 @@ -package electrosphere.server.ai.nodes.plan; - -import electrosphere.entity.Entity; -import electrosphere.server.ai.blackboard.Blackboard; -import electrosphere.server.ai.nodes.AITreeNode; - -/** - * A node that performs functions to try to build a structure - */ -public class BuildStructureNode implements AITreeNode { - - @Override - public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { - return AITreeNodeResult.SUCCESS; - } -} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/FindEntityTargetNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/FindEntityTargetNode.java deleted file mode 100644 index 40b5fc07..00000000 --- a/src/main/java/electrosphere/server/ai/nodes/plan/FindEntityTargetNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package electrosphere.server.ai.nodes.plan; - -import electrosphere.entity.Entity; -import electrosphere.entity.state.block.ServerBlockTree; -import electrosphere.server.ai.blackboard.Blackboard; -import electrosphere.server.ai.blackboard.BlackboardKeys; -import electrosphere.server.ai.nodes.AITreeNode; - -/** - * Finds a target given some criteria - */ -public class FindEntityTargetNode implements AITreeNode { - - @Override - public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ - if(ServerBlockTree.getServerBlockTree(entity) != null){ - ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(entity); - if(serverBlockTree.isIdle()){ - serverBlockTree.start(); - return AITreeNodeResult.SUCCESS; - } else { - return AITreeNodeResult.RUNNING; - } - } else { - return AITreeNodeResult.FAILURE; - } - } - - /** - * Sets the target in the blackboard - * @param blackboard The blackboard - * @param target The target - */ - public static void setTarget(Blackboard blackboard, Entity target){ - blackboard.put(BlackboardKeys.ENTITY_TARGET, target); - } - - /** - * Gets the currently targeted entity - * @param blackboard The blackboard - * @return The entity target if it exists, null otherwise - */ - public static Entity getTarget(Blackboard blackboard){ - return (Entity)blackboard.get(BlackboardKeys.ENTITY_TARGET); - } - - /** - * Checks if the blackboard has a currently targeted entity - * @param blackboard The blackboard - * @return true if it has a currently targeted entity, false otherwise - */ - public static boolean hasTarget(Blackboard blackboard){ - return blackboard.has(BlackboardKeys.ENTITY_TARGET); - } - -} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/SetBuildGoalNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/SetBuildGoalNode.java deleted file mode 100644 index d4a9f1cb..00000000 --- a/src/main/java/electrosphere/server/ai/nodes/plan/SetBuildGoalNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package electrosphere.server.ai.nodes.plan; - -import electrosphere.entity.Entity; -import electrosphere.server.ai.blackboard.Blackboard; -import electrosphere.server.ai.nodes.AITreeNode; - -/** - * Sets the type of structure to build - */ -public class SetBuildGoalNode implements AITreeNode { - - /** - * A shelter structure type - */ - public static final String STRUCTURE_TYPE_SHELTER = "shelter"; - - /** - * The type of structure - */ - String type; - - /** - * Constructor - * @param type The type to set the build goal to - */ - public SetBuildGoalNode(String type){ - this.type = type; - } - - @Override - public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ - 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 new file mode 100644 index 00000000..52871548 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java @@ -0,0 +1,106 @@ +package electrosphere.server.ai.nodes.plan; + +import electrosphere.entity.Entity; +import electrosphere.game.data.item.source.ItemSourcingData; +import electrosphere.game.data.item.source.ItemSourcingTree; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.actions.interact.HarvestNode; + +/** + * Solves a dependency tree for how to acquire a given item + */ +public class SolveSourcingTreeNode implements AITreeNode { + + /** + * Blackboard key that stores the id of the item to source + */ + String itemIdKey; + + /** + * Constructor + * @param itemIdKey The blackboard key that stores the id of the item to calculate sourcing for + */ + public SolveSourcingTreeNode(String itemIdKey){ + this.itemIdKey = itemIdKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + if(!blackboard.has(itemIdKey)){ + return AITreeNodeResult.FAILURE; + } + if(!SolveSourcingTreeNode.hasItemSourcingTree(blackboard) || !SolveSourcingTreeNode.getItemSourcingTree(blackboard).getRootItem().equals(this.itemIdKey)){ + String itemId = (String)blackboard.get(itemIdKey); + ItemSourcingTree sourcingTree = ItemSourcingTree.create(itemId); + SolveSourcingTreeNode.setItemSourcingTree(blackboard, sourcingTree); + } + ItemSourcingTree sourcingTree = SolveSourcingTreeNode.getItemSourcingTree(blackboard); + ItemSourcingData sourcingData = sourcingTree.getCurrentDependency(entity); + if(sourcingData == null){ + throw new Error("Source data is null!"); + } + //set the type to harvest if this is a harvest type + if(sourcingData.getHarvestTargets().size() > 0){ + HarvestNode.setHarvestTargetType(blackboard, sourcingData.getHarvestTargets().get(0).getId()); + } + SolveSourcingTreeNode.setItemSourcingData(blackboard, sourcingData); + return AITreeNodeResult.SUCCESS; + } + + /** + * Sets the item sourcing tree of the blackboard + * @param blackboard The blackboard + * @param tree The tree + */ + public static void setItemSourcingTree(Blackboard blackboard, ItemSourcingTree tree){ + blackboard.put(BlackboardKeys.ITEM_SOURCING_TREE, tree); + } + + /** + * Checks if the blackboard has an item sourcing tree + * @param blackboard The blackboard + * @return The item sourcing tree + */ + public static boolean hasItemSourcingTree(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.ITEM_SOURCING_TREE); + } + + /** + * Gets the item sourcing tree of the blackboard + * @param blackboard The blackboard + * @return The item sourcing tree + */ + public static ItemSourcingTree getItemSourcingTree(Blackboard blackboard){ + return (ItemSourcingTree)blackboard.get(BlackboardKeys.ITEM_SOURCING_TREE); + } + + /** + * Sets the item sourcing data of the blackboard + * @param blackboard The blackboard + * @param tree The data + */ + public static void setItemSourcingData(Blackboard blackboard, ItemSourcingData data){ + blackboard.put(BlackboardKeys.ITEM_SOURCING_DATA, data); + } + + /** + * Checks if the blackboard has an item sourcing data + * @param blackboard The blackboard + * @return The item sourcing data + */ + public static boolean hasItemSourcingData(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.ITEM_SOURCING_DATA); + } + + /** + * Gets the item sourcing data of the blackboard + * @param blackboard The blackboard + * @return The item sourcing data + */ + public static ItemSourcingData getItemSourcingData(Blackboard blackboard){ + return (ItemSourcingData)blackboard.get(BlackboardKeys.ITEM_SOURCING_DATA); + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/TargetEntityCategoryNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/TargetEntityCategoryNode.java new file mode 100644 index 00000000..bac694ca --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/plan/TargetEntityCategoryNode.java @@ -0,0 +1,106 @@ +package electrosphere.server.ai.nodes.plan; + +import java.util.Collection; + +import electrosphere.entity.Entity; +import electrosphere.entity.types.common.CommonEntityUtils; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.services.NearbyEntityService; + +/** + * Targets a nearby entity of a specific type + */ +public class TargetEntityCategoryNode implements AITreeNode { + + + /** + * The blackboard key to pull the entity type from + */ + String sourceKey; + + /** + * Constructor + * @param sourceKey The blackboard key to pull the entity type from + */ + public TargetEntityCategoryNode(String sourceKey){ + this.sourceKey = sourceKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + String goalEntityId = (String)blackboard.get(sourceKey); + + if(goalEntityId == null){ + throw new Error("Entity id to search for is null!"); + } + + if(TargetEntityCategoryNode.hasTarget(blackboard)){ + Entity currentTarget = TargetEntityCategoryNode.getTarget(blackboard); + if(currentTarget == null){ + TargetEntityCategoryNode.clearTarget(blackboard); + } else { + String typeId = CommonEntityUtils.getEntitySubtype(currentTarget); + if(!typeId.equals(goalEntityId)){ + TargetEntityCategoryNode.clearTarget(blackboard); + } + } + } + if(!TargetEntityCategoryNode.hasTarget(blackboard)){ + Collection nearbyEntities = NearbyEntityService.getNearbyEntities(blackboard); + for(Entity potential : nearbyEntities){ + //get id -- skip empty ids + String potentialId = CommonEntityUtils.getEntitySubtype(potential); + if(potentialId == null){ + continue; + } + //set to target if id match + if(potentialId.equals(goalEntityId)){ + TargetEntityCategoryNode.setTarget(blackboard, potential); + break; + } + } + } + if(!TargetEntityCategoryNode.hasTarget(blackboard)){ + return AITreeNodeResult.FAILURE; + } + return AITreeNodeResult.SUCCESS; + } + + /** + * Sets the target in the blackboard + * @param blackboard The blackboard + * @param target The target + */ + public static void setTarget(Blackboard blackboard, Entity target){ + blackboard.put(BlackboardKeys.ENTITY_TARGET, target); + } + + /** + * Gets the currently targeted entity + * @param blackboard The blackboard + * @return The entity target if it exists, null otherwise + */ + public static Entity getTarget(Blackboard blackboard){ + return (Entity)blackboard.get(BlackboardKeys.ENTITY_TARGET); + } + + /** + * Checks if the blackboard has a currently targeted entity + * @param blackboard The blackboard + * @return true if it has a currently targeted entity, false otherwise + */ + public static boolean hasTarget(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.ENTITY_TARGET); + } + + /** + * Clears the target + * @param blackboard The target + */ + public static void clearTarget(Blackboard blackboard){ + blackboard.delete(BlackboardKeys.ENTITY_TARGET); + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/TargetPositionNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/TargetPositionNode.java index 69375fb5..f7bafc26 100644 --- a/src/main/java/electrosphere/server/ai/nodes/plan/TargetPositionNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/plan/TargetPositionNode.java @@ -13,13 +13,32 @@ import electrosphere.server.ai.nodes.AITreeNode; */ public class TargetPositionNode implements AITreeNode { + /** + * The key to lookup the target under + */ + String targetKey; + + /** + * constructor + * @param targetKey The key to lookup the target under + */ + public TargetPositionNode(String targetKey){ + this.targetKey = targetKey; + } + @Override public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ - if(!FindEntityTargetNode.hasTarget(blackboard)){ - return AITreeNodeResult.FAILURE; + Object targetRaw = blackboard.get(this.targetKey); + Vector3d targetPos = null; + if(targetRaw == null){ + throw new Error("Target undefined!"); + } + if(targetRaw instanceof Vector3d){ + targetPos = (Vector3d)targetRaw; + } + if(targetRaw instanceof Entity){ + targetPos = EntityUtils.getPosition((Entity)targetRaw); } - Entity target = FindEntityTargetNode.getTarget(blackboard); - Vector3d targetPos = EntityUtils.getPosition(target); TargetPositionNode.setMoveToTarget(blackboard, targetPos); return AITreeNodeResult.SUCCESS; } diff --git a/src/main/java/electrosphere/server/ai/nodes/solvers/FindEntityTargetNode.java b/src/main/java/electrosphere/server/ai/nodes/solvers/FindEntityTargetNode.java new file mode 100644 index 00000000..e15c8b58 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/solvers/FindEntityTargetNode.java @@ -0,0 +1,28 @@ +package electrosphere.server.ai.nodes.solvers; + +import electrosphere.entity.Entity; +import electrosphere.entity.state.block.ServerBlockTree; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Finds a target given some criteria + */ +public class FindEntityTargetNode implements AITreeNode { + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ + if(ServerBlockTree.getServerBlockTree(entity) != null){ + ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(entity); + if(serverBlockTree.isIdle()){ + serverBlockTree.start(); + return AITreeNodeResult.SUCCESS; + } else { + return AITreeNodeResult.RUNNING; + } + } else { + return AITreeNodeResult.FAILURE; + } + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/solvers/SolveBuildMaterialNode.java b/src/main/java/electrosphere/server/ai/nodes/solvers/SolveBuildMaterialNode.java new file mode 100644 index 00000000..b45e1e1e --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/solvers/SolveBuildMaterialNode.java @@ -0,0 +1,47 @@ +package electrosphere.server.ai.nodes.solvers; + +import org.joml.Vector3i; + +import electrosphere.engine.Globals; +import electrosphere.entity.Entity; +import electrosphere.game.data.block.BlockFab; +import electrosphere.game.data.block.BlockType; +import electrosphere.game.data.item.Item; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.checks.spatial.BeginStructureNode; +import electrosphere.server.ai.trees.struct.BuildStructureTree; +import electrosphere.server.datacell.Realm; +import electrosphere.server.macro.structure.Structure; +import electrosphere.server.macro.utils.StructureRepairUtils; + +/** + * Solves for the current build material + */ +public class SolveBuildMaterialNode implements AITreeNode { + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + if(!BuildStructureTree.hasCurrentMaterial(blackboard)){ + Structure struct = BeginStructureNode.getStructureTarget(blackboard); + if(!struct.isRepairable()){ + return AITreeNodeResult.FAILURE; + } + //solve for repairable block + Realm realm = Globals.realmManager.getEntityRealm(entity); + Vector3i repairPos = StructureRepairUtils.getRepairablePosition(realm, struct); + + //get the id of item entity type for the block we need + BlockFab fab = struct.getFab(); + short blockTypeId = fab.getType(repairPos.x, repairPos.y, repairPos.z); + BlockType blockType = Globals.gameConfigCurrent.getBlockData().getTypeFromId(blockTypeId); + String itemId = Item.getBlockTypeId(blockType); + + //store + BuildStructureTree.setCurrentMaterial(blackboard, itemId); + } + + return AITreeNodeResult.SUCCESS; + } + +} diff --git a/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java new file mode 100644 index 00000000..108d2620 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java @@ -0,0 +1,66 @@ +package electrosphere.server.ai.trees.creature; + +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.HarvestNode; +import electrosphere.server.ai.nodes.checks.inventory.SourcingTypeNode; +import electrosphere.server.ai.nodes.meta.collections.SelectorNode; +import electrosphere.server.ai.nodes.meta.collections.SequenceNode; +import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; +import electrosphere.server.ai.nodes.meta.decorators.RunnerNode; +import electrosphere.server.ai.nodes.meta.decorators.SucceederNode; +import electrosphere.server.ai.nodes.plan.SolveSourcingTreeNode; +import electrosphere.server.ai.nodes.plan.TargetEntityCategoryNode; + +/** + * A tree to acquire an item + */ +public class AcquireItemTree { + + /** + * Name of the tree + */ + public static final String TREE_NAME = "AcquireItem"; + + /** + * Creates a acquire-item tree + * @param key The blackboard key to search for the item name under + * @return The root node of the acquire-item tree + */ + public static AITreeNode create(String blackboardKey){ + return new SequenceNode( + new PublishStatusNode("Acquire an item"), + //solve how we're going to get this top level item + new SolveSourcingTreeNode(blackboardKey), + new SelectorNode( + 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 RunnerNode(null) + ), + new SequenceNode( + new PublishStatusNode("Harvest an item"), + //check if we should be sourcing this from harvesting foliage + new SourcingTypeNode(SourcingType.HARVEST), + new TargetEntityCategoryNode(BlackboardKeys.HARVEST_TARGET_TYPE), + MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET), + new HarvestNode(), + new RunnerNode(null) + ), + new SequenceNode( + new PublishStatusNode("Fell a tree"), + //check if we should be sourcing this from felling a tree + new SourcingTypeNode(SourcingType.TREE), + //TODO: logic to fell a tree + new RunnerNode(null) + ) + ), + new SucceederNode(null) + ); + } + +} diff --git a/src/main/java/electrosphere/server/ai/trees/creature/MoveToTarget.java b/src/main/java/electrosphere/server/ai/trees/creature/MoveToTarget.java index c0f5d72a..cbe3ee90 100644 --- a/src/main/java/electrosphere/server/ai/trees/creature/MoveToTarget.java +++ b/src/main/java/electrosphere/server/ai/trees/creature/MoveToTarget.java @@ -5,12 +5,11 @@ import electrosphere.server.ai.nodes.AITreeNode; import electrosphere.server.ai.nodes.actions.move.FaceTargetNode; import electrosphere.server.ai.nodes.actions.move.MoveStartNode; import electrosphere.server.ai.nodes.actions.move.MoveStopNode; -import electrosphere.server.ai.nodes.checks.spatial.TargetRangeCheck; +import electrosphere.server.ai.nodes.checks.spatial.TargetRangeCheckNode; import electrosphere.server.ai.nodes.meta.collections.SelectorNode; import electrosphere.server.ai.nodes.meta.collections.SequenceNode; import electrosphere.server.ai.nodes.meta.decorators.RunnerNode; import electrosphere.server.ai.nodes.meta.decorators.SucceederNode; -import electrosphere.server.ai.nodes.plan.TargetPositionNode; /** * Moves to a target @@ -25,22 +24,22 @@ public class MoveToTarget { /** * Creates a move-to-target tree * @param dist The target distance to be within + * @param targetKey The key to lookup the target under * @return The root node of the move-to-target tree */ - public static AITreeNode create(double dist){ + public static AITreeNode create(double dist, String targetKey){ return new SelectorNode( new SequenceNode( //check if in range of target - new TargetRangeCheck(0), + new TargetRangeCheckNode(dist, targetKey), //if in range, stop moving fowards and return SUCCESS new SucceederNode(new MoveStopNode()) ), //not in range of target, keep moving towards it new SequenceNode( - new TargetPositionNode(), //check that dependencies exist - new FaceTargetNode(), + new FaceTargetNode(targetKey), new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD)) ) ); diff --git a/src/main/java/electrosphere/server/ai/trees/creature/melee/MeleeAITree.java b/src/main/java/electrosphere/server/ai/trees/creature/melee/MeleeAITree.java index 2e69bf86..8934ec73 100644 --- a/src/main/java/electrosphere/server/ai/trees/creature/melee/MeleeAITree.java +++ b/src/main/java/electrosphere/server/ai/trees/creature/melee/MeleeAITree.java @@ -2,6 +2,7 @@ package electrosphere.server.ai.trees.creature.melee; import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing; import electrosphere.game.data.creature.type.ai.AttackerTreeData; +import electrosphere.server.ai.blackboard.BlackboardKeys; import electrosphere.server.ai.nodes.AITreeNode; import electrosphere.server.ai.nodes.AITreeNode.AITreeNodeResult; import electrosphere.server.ai.nodes.actions.combat.AttackStartNode; @@ -66,19 +67,19 @@ public class MeleeAITree { //wait new SequenceNode( new PublishStatusNode("Waiting"), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new TimerNode(new SucceederNode(null), 600) ), //wait new SequenceNode( new PublishStatusNode("Waiting"), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new TimerNode(new SucceederNode(null), 300) ), //attack new SequenceNode( new PublishStatusNode("Attacking"), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new AttackStartNode(), new TimerNode(new SucceederNode(null), 300) ) @@ -96,7 +97,7 @@ public class MeleeAITree { //wait new SequenceNode( new PublishStatusNode("Waiting"), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new TimerNode(new SucceederNode(null), 1200) ), @@ -108,7 +109,7 @@ public class MeleeAITree { new MoveStartNode(MovementRelativeFacing.RIGHT), new FailerNode(null) )), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new TimerNode(new SucceederNode(null), 600), new SucceederNode(new WalkStopNode()), new SucceederNode(new MoveStopNode()) @@ -122,7 +123,7 @@ public class MeleeAITree { new MoveStartNode(MovementRelativeFacing.LEFT), new FailerNode(null) )), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new TimerNode(new SucceederNode(null), 600), new SucceederNode(new WalkStopNode()), new SucceederNode(new MoveStopNode()) @@ -132,7 +133,7 @@ public class MeleeAITree { //move towards target and attack new SequenceNode( new PublishStatusNode("Move into attack range"), - new FaceTargetNode(), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), new SucceederNode(new MoveStartNode(MovementRelativeFacing.FORWARD)), new TimerNode(new SucceederNode(null), 600) ) diff --git a/src/main/java/electrosphere/server/ai/trees/hierarchy/safety/shelter/ConstructShelterTree.java b/src/main/java/electrosphere/server/ai/trees/hierarchy/safety/shelter/ConstructShelterTree.java index 5139cb87..64a9dd7d 100644 --- a/src/main/java/electrosphere/server/ai/trees/hierarchy/safety/shelter/ConstructShelterTree.java +++ b/src/main/java/electrosphere/server/ai/trees/hierarchy/safety/shelter/ConstructShelterTree.java @@ -1,9 +1,12 @@ package electrosphere.server.ai.trees.hierarchy.safety.shelter; +import electrosphere.engine.Globals; import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.checks.spatial.BeginStructureNode; import electrosphere.server.ai.nodes.meta.collections.SequenceNode; import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; import electrosphere.server.ai.nodes.meta.decorators.SucceederNode; +import electrosphere.server.ai.trees.struct.BuildStructureTree; /** * Tree for constructing shelter @@ -22,6 +25,8 @@ public class ConstructShelterTree { public static AITreeNode create(){ return new SequenceNode( new PublishStatusNode("Construct a shelter"), + new BeginStructureNode(Globals.gameConfigCurrent.getStructureData().getTypes().iterator().next()), + BuildStructureTree.create(), new SucceederNode(null) ); } diff --git a/src/main/java/electrosphere/server/ai/trees/struct/BuildStructureTree.java b/src/main/java/electrosphere/server/ai/trees/struct/BuildStructureTree.java new file mode 100644 index 00000000..020d3744 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/trees/struct/BuildStructureTree.java @@ -0,0 +1,93 @@ +package electrosphere.server.ai.trees.struct; + +import electrosphere.collision.CollisionEngine; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.actions.interact.PlaceBlockNode; +import electrosphere.server.ai.nodes.checks.inventory.InventoryContainsNode; +import electrosphere.server.ai.nodes.meta.collections.SelectorNode; +import electrosphere.server.ai.nodes.meta.collections.SequenceNode; +import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; +import electrosphere.server.ai.nodes.meta.decorators.RunnerNode; +import electrosphere.server.ai.nodes.solvers.SolveBuildMaterialNode; +import electrosphere.server.ai.trees.creature.AcquireItemTree; +import electrosphere.server.ai.trees.creature.MoveToTarget; + +/** + * A tree to build whatever the current structure target is + */ +public class BuildStructureTree { + + /** + * Name of the tree + */ + public static final String TREE_NAME = "ConstructShelterTree"; + + /** + * Creates a construct shelter tree + * @return The root node of the tree + */ + public static AITreeNode create(){ + return new SequenceNode( + new PublishStatusNode("Construct a structure"), + //figure out current task + new SelectorNode( + //make sure we know what material we need to build with currently + new SequenceNode( + new SolveBuildMaterialNode(), + new PublishStatusNode("Trying to place block in structure"), + //if has building materials + new SequenceNode( + new InventoryContainsNode(BlackboardKeys.BUILDING_MATERIAL_CURRENT), + //if we're within range to place the material + new SelectorNode( + //in range, place block + new PlaceBlockNode(), + //not in range, move to within range + new SequenceNode( + //TODO: Solve for where to move towards + MoveToTarget.create(CollisionEngine.DEFAULT_INTERACT_DISTANCE, BlackboardKeys.ENTITY_TARGET) + ) + ) + ) + ), + //does not have building materials + new SequenceNode( + new PublishStatusNode("Acquire building material"), + //try to find building materials + AcquireItemTree.create(BlackboardKeys.BUILDING_MATERIAL_CURRENT), + new RunnerNode(null) + ) + ) + ); + } + + /** + * Sets the current needed building material + * @param blackboard The blackboard + * @param entityTypeId The id of the material + */ + public static void setCurrentMaterial(Blackboard blackboard, String entityTypeId){ + blackboard.put(BlackboardKeys.BUILDING_MATERIAL_CURRENT, entityTypeId); + } + + /** + * Checks if the blackboard stores the currently sought after material + * @param blackboard The blackboard + * @return true if there is a currently desired material, false otherwise + */ + public static boolean hasCurrentMaterial(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.BUILDING_MATERIAL_CURRENT); + } + + /** + * Gets the currently sought after material + * @param blackboard The blackboard + * @return The id of the entity type of the sought after material if it exists, null otherwise + */ + public static String getCurrentMaterial(Blackboard blackboard){ + return (String)blackboard.get(BlackboardKeys.BUILDING_MATERIAL_CURRENT); + } + +} diff --git a/src/main/java/electrosphere/server/ai/trees/test/BlockerAITree.java b/src/main/java/electrosphere/server/ai/trees/test/BlockerAITree.java index 6595376f..a537f50b 100644 --- a/src/main/java/electrosphere/server/ai/trees/test/BlockerAITree.java +++ b/src/main/java/electrosphere/server/ai/trees/test/BlockerAITree.java @@ -1,6 +1,7 @@ package electrosphere.server.ai.trees.test; import electrosphere.game.data.creature.type.ai.BlockerTreeData; +import electrosphere.server.ai.blackboard.BlackboardKeys; import electrosphere.server.ai.nodes.AITreeNode; import electrosphere.server.ai.nodes.actions.BlockStartNode; import electrosphere.server.ai.nodes.actions.combat.MeleeTargetingNode; @@ -25,7 +26,7 @@ public class BlockerAITree { return new SequenceNode( new BlockStartNode(), new MeleeTargetingNode(5.0f), - new FaceTargetNode() + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET) ); } diff --git a/src/main/java/electrosphere/server/datacell/ServerWorldData.java b/src/main/java/electrosphere/server/datacell/ServerWorldData.java index 52e22ae6..5f469482 100644 --- a/src/main/java/electrosphere/server/datacell/ServerWorldData.java +++ b/src/main/java/electrosphere/server/datacell/ServerWorldData.java @@ -248,7 +248,7 @@ public class ServerWorldData { * @param real The real position * @return The local block grid position */ - public int convertRealToLocalBlockSpace(double real){ + public static int convertRealToLocalBlockSpace(double real){ return (int)Math.floor(real * BlockChunkData.BLOCKS_PER_UNIT_DISTANCE % BlockChunkData.CHUNK_DATA_WIDTH); } @@ -350,7 +350,7 @@ public class ServerWorldData { * @param position The real coordinate * @return The local block grid space coordinate */ - public Vector3i convertRealToLocalBlockSpace(Vector3d position){ + public static Vector3i convertRealToLocalBlockSpace(Vector3d position){ return new Vector3i( convertRealToLocalBlockSpace(position.x), convertRealToLocalBlockSpace(position.y), diff --git a/src/main/java/electrosphere/server/entity/serialization/ContentSerialization.java b/src/main/java/electrosphere/server/entity/serialization/ContentSerialization.java index ea3ffc08..7fde26ce 100644 --- a/src/main/java/electrosphere/server/entity/serialization/ContentSerialization.java +++ b/src/main/java/electrosphere/server/entity/serialization/ContentSerialization.java @@ -43,6 +43,10 @@ public class ContentSerialization { for(Entity entity : entities){ if(!CreatureUtils.hasControllerPlayerId(entity) && !ServerCharacterData.hasServerCharacterDataTree(entity)){ EntityType type = CommonEntityUtils.getEntityType(entity); + if(type == EntityType.ENGINE){ + //do not serialize engine entities + continue; + } if(type != null){ EntitySerialization serializedEntity = constructEntitySerialization(entity); rVal.serializedEntities.add(serializedEntity); @@ -114,6 +118,9 @@ public class ContentSerialization { */ public static Entity serverHydrateEntitySerialization(Realm realm, EntitySerialization serializedEntity){ Entity rVal = null; + if(serializedEntity.getSubtype() == null){ + throw new Error("Subtype undefined!"); + } switch(EntityTypes.fromInt(serializedEntity.getType())){ case CREATURE: { CreatureTemplate template = null; diff --git a/src/main/java/electrosphere/server/macro/MacroData.java b/src/main/java/electrosphere/server/macro/MacroData.java index cd6a036a..34ab3d71 100644 --- a/src/main/java/electrosphere/server/macro/MacroData.java +++ b/src/main/java/electrosphere/server/macro/MacroData.java @@ -104,17 +104,17 @@ public class MacroData { } //add a test character - Character testChar = new Character(); - testChar.setPos(new Vector3d(ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 3, 32769)))); - Race.setRace(testChar, Race.create("human", "human")); - rVal.characters.add(testChar); + // Character testChar = new Character(); + // testChar.setPos(new Vector3d(ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 3, 32769)))); + // Race.setRace(testChar, Race.create("human", "human")); + // rVal.characters.add(testChar); //add a test character - Vector3d structPos = ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 0, 32770)); - double elevationAtStruct = serverWorldData.getServerTerrainManager().getElevation(32774, 32770, 0, 0); - structPos.y = elevationAtStruct; - Structure struct = Structure.createStructure(Globals.gameConfigCurrent.getStructureData().getType("test1"),structPos); - rVal.structures.add(struct); + // Vector3d structPos = ServerWorldData.convertChunkToRealSpace(new Vector3i(32774, 0, 32770)); + // double elevationAtStruct = serverWorldData.getServerTerrainManager().getElevation(32774, 32770, 0, 0); + // structPos.y = elevationAtStruct; + // Structure struct = Structure.createStructure(Globals.gameConfigCurrent.getStructureData().getType("test1"),structPos); + // rVal.structures.add(struct); //spawn initial characters in each race //find initial positions to place characters at per race diff --git a/src/main/java/electrosphere/server/macro/structure/Structure.java b/src/main/java/electrosphere/server/macro/structure/Structure.java index 305577e7..49dee5c1 100644 --- a/src/main/java/electrosphere/server/macro/structure/Structure.java +++ b/src/main/java/electrosphere/server/macro/structure/Structure.java @@ -41,6 +41,11 @@ public class Structure extends CharacterData implements MacroAreaObject { */ String type; + /** + * Tracks whether this structure needs repairs or not + */ + boolean repairable = false; + /** * Constructor * @param dataType The data type of the structure @@ -125,6 +130,22 @@ public class Structure extends CharacterData implements MacroAreaObject { public void setFab(BlockFab fab) { this.fab = fab; } + + /** + * Checks if the structure is repairable + * @return true if it is repairable, false otherwise + */ + public boolean isRepairable() { + return repairable; + } + + /** + * Sets whether this structure is repairable or not + * @param repairable true if it is repairable, false otherwise + */ + public void setRepairable(boolean repairable) { + this.repairable = repairable; + } } diff --git a/src/main/java/electrosphere/server/macro/utils/StructurePlacementUtils.java b/src/main/java/electrosphere/server/macro/utils/StructurePlacementUtils.java new file mode 100644 index 00000000..1d98d244 --- /dev/null +++ b/src/main/java/electrosphere/server/macro/utils/StructurePlacementUtils.java @@ -0,0 +1,24 @@ +package electrosphere.server.macro.utils; + +import org.joml.Vector3d; + +import electrosphere.game.data.struct.StructureData; +import electrosphere.server.macro.MacroData; + +/** + * Utilities for placing structures + */ +public class StructurePlacementUtils { + + /** + * Gets an optimal position to place a structure + * @param macroData The macro data + * @param structureData The data for the structure + * @param approxLocation The location to start searching from + * @return The position + */ + public static Vector3d getPlacementPosition(MacroData macroData, StructureData structureData, Vector3d approxLocation){ + return approxLocation; + } + +} diff --git a/src/main/java/electrosphere/server/macro/utils/StructureRepairUtils.java b/src/main/java/electrosphere/server/macro/utils/StructureRepairUtils.java new file mode 100644 index 00000000..50cad024 --- /dev/null +++ b/src/main/java/electrosphere/server/macro/utils/StructureRepairUtils.java @@ -0,0 +1,49 @@ +package electrosphere.server.macro.utils; + +import org.joml.Vector3d; +import org.joml.Vector3i; + +import electrosphere.client.block.BlockChunkData; +import electrosphere.game.data.block.BlockFab; +import electrosphere.server.datacell.Realm; +import electrosphere.server.datacell.ServerWorldData; +import electrosphere.server.datacell.gridded.GriddedDataCellManager; +import electrosphere.server.macro.structure.Structure; + +/** + * Utilities for repairing a structure + */ +public class StructureRepairUtils { + + /** + * Solves for the next position in the structure's fab that can be repaired + * @param realm The realm the structure is within + * @param struct The structure + * @return The next position that can be repaired if it exists, null otherwise + */ + public static Vector3i getRepairablePosition(Realm realm, Structure struct){ + //error checking + if(!(realm.getDataCellManager() instanceof GriddedDataCellManager)){ + throw new Error("Realm is not a gridded realm!"); + } + + BlockFab fab = struct.getFab(); + Vector3d structStartPos = struct.getStartPos(); + GriddedDataCellManager griddedDataCellManager = (GriddedDataCellManager)realm.getDataCellManager(); + for(int x = 0; x < fab.getDimensions().x; x++){ + for(int y = 0; y < fab.getDimensions().y; y++){ + for(int z = 0; z < fab.getDimensions().z; z++){ + Vector3d offsetPos = new Vector3d(structStartPos).add(x,y,z); + Vector3i chunkPos = ServerWorldData.convertRealToChunkSpace(offsetPos); + Vector3i blockPos = ServerWorldData.convertRealToLocalBlockSpace(offsetPos); + BlockChunkData blockChunkData = griddedDataCellManager.getBlocksAtPosition(chunkPos); + if(blockChunkData.getType(blockPos.x, blockPos.y, blockPos.z) != fab.getType(x, y, z)){ + return new Vector3i(x,y,z); + } + } + } + } + return null; + } + +}