From af0e30dd9594359fee801434f002a46d5f162d25 Mon Sep 17 00:00:00 2001 From: austin Date: Thu, 1 May 2025 22:21:12 -0400 Subject: [PATCH] ai tool equipping --- assets/Data/entity/items/weapons.json | 3 +- docs/src/progress/renderertodo.md | 2 + .../entity/state/attack/ClientAttackTree.java | 48 ++++++---- .../actions/inventory/EquipToolbarNode.java | 95 +++++++++++++++++++ .../inventory/InventoryContainsNode.java | 35 +------ .../checks/spatial/TargetRangeCheckNode.java | 2 +- .../server/ai/nodes/meta/DataStorageNode.java | 38 ++++++++ .../ai/nodes/plan/SolveSourcingTreeNode.java | 2 + .../ai/trees/creature/AcquireItemTree.java | 4 +- .../creature/inventory/EquipToolbarTree.java | 38 ++++++++ .../ai/trees/creature/melee/FellTree.java | 79 +++++++++++++++ 11 files changed, 291 insertions(+), 55 deletions(-) create mode 100644 src/main/java/electrosphere/server/ai/nodes/actions/inventory/EquipToolbarNode.java create mode 100644 src/main/java/electrosphere/server/ai/nodes/meta/DataStorageNode.java create mode 100644 src/main/java/electrosphere/server/ai/trees/creature/inventory/EquipToolbarTree.java create mode 100644 src/main/java/electrosphere/server/ai/trees/creature/melee/FellTree.java diff --git a/assets/Data/entity/items/weapons.json b/assets/Data/entity/items/weapons.json index 2b56808b..ab909d99 100644 --- a/assets/Data/entity/items/weapons.json +++ b/assets/Data/entity/items/weapons.json @@ -200,7 +200,8 @@ "GRAVITY", "MELEE", "TARGETABLE", - "OUTLINE" + "OUTLINE", + "AXE" ], "graphicsTemplate": { "model": { diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 8fc152f7..4685afca 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1636,6 +1636,8 @@ Many new AI trees - AI can harvest entities - AI can pick up items - AI can craft + - AI can equip tools + - AI can fell trees diff --git a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java index e90f1110..dd06eab8 100644 --- a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java +++ b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java @@ -358,13 +358,15 @@ public class ClientAttackTree implements BehaviorTree { } } } - if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ - HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); - for(String boneName : this.currentMove.getActiveBones()){ - List hitboxes = hitboxCollectionState.getHitboxes(boneName); - for(HitboxState hitbox : hitboxes){ - if(hitbox.getType() == HitboxType.HIT){ - hitbox.setActive(true); + if(this.currentMove != null){ + if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ + HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); + for(String boneName : this.currentMove.getActiveBones()){ + List hitboxes = hitboxCollectionState.getHitboxes(boneName); + for(HitboxState hitbox : hitboxes){ + if(hitbox.getType() == HitboxType.HIT){ + hitbox.setActive(true); + } } } } @@ -382,13 +384,15 @@ public class ClientAttackTree implements BehaviorTree { } } } - if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ - HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); - for(String boneName : this.currentMove.getActiveBones()){ - List hitboxes = hitboxCollectionState.getHitboxes(boneName); - for(HitboxState hitbox : hitboxes){ - if(hitbox.getType() == HitboxType.HIT){ - hitbox.setActive(false); + if(this.currentMove != null){ + if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ + HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); + for(String boneName : this.currentMove.getActiveBones()){ + List hitboxes = hitboxCollectionState.getHitboxes(boneName); + for(HitboxState hitbox : hitboxes){ + if(hitbox.getType() == HitboxType.HIT){ + hitbox.setActive(false); + } } } } @@ -406,13 +410,15 @@ public class ClientAttackTree implements BehaviorTree { } } } - if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ - HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); - for(String boneName : this.currentMove.getActiveBones()){ - List hitboxes = hitboxCollectionState.getHitboxes(boneName); - for(HitboxState hitbox : hitboxes){ - if(hitbox.getType() == HitboxType.HIT){ - hitbox.setActive(false); + if(this.currentMove != null){ + if(this.currentMove.getActiveBones() != null && HitboxCollectionState.hasHitboxState(this.parent)){ + HitboxCollectionState hitboxCollectionState = HitboxCollectionState.getHitboxState(this.parent); + for(String boneName : this.currentMove.getActiveBones()){ + List hitboxes = hitboxCollectionState.getHitboxes(boneName); + for(HitboxState hitbox : hitboxes){ + if(hitbox.getType() == HitboxType.HIT){ + hitbox.setActive(false); + } } } } diff --git a/src/main/java/electrosphere/server/ai/nodes/actions/inventory/EquipToolbarNode.java b/src/main/java/electrosphere/server/ai/nodes/actions/inventory/EquipToolbarNode.java new file mode 100644 index 00000000..a561f02b --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/actions/inventory/EquipToolbarNode.java @@ -0,0 +1,95 @@ +package electrosphere.server.ai.nodes.actions.inventory; + +import electrosphere.entity.Entity; +import electrosphere.entity.state.equip.ServerToolbarState; +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.nodes.AITreeNode; + +/** + * Tries to equip a type of item to the toolbar and select that item in the toolbar + */ +public class EquipToolbarNode implements AITreeNode { + + /** + * Key to lookup the item type under + */ + String itemTypeKey; + + /** + * Constructor + * @param itemTypeKey The blackboard key to lookup the item type under + */ + public EquipToolbarNode(String itemTypeKey){ + this.itemTypeKey = itemTypeKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + String targetItemType = (String)blackboard.get(itemTypeKey); + + //check if that item type is already equipped + ServerToolbarState serverToolbarState = ServerToolbarState.getServerToolbarState(entity); + if(serverToolbarState.getRealWorldItem() != null){ + Entity realWorldItem = serverToolbarState.getRealWorldItem(); + String type = CommonEntityUtils.getEntitySubtype(realWorldItem); + if(type.equals(targetItemType)){ + return AITreeNodeResult.SUCCESS; + } + } + + //check if this item type is already in toolbar and we can just swap to it + if(InventoryUtils.hasToolbarInventory(entity)){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(entity); + for(Entity itemEnt : toolbarInventory.getItems()){ + if(itemEnt == null){ + continue; + } + String type = CommonEntityUtils.getEntitySubtype(itemEnt); + if(type.equals(targetItemType)){ + String slotId = toolbarInventory.getItemSlot(itemEnt); + serverToolbarState.attemptChangeSelection(Integer.parseInt(slotId)); + return AITreeNodeResult.SUCCESS; + } + } + } + + //make sure we have a free toolbar slot + int freeToolbarSlot = 0; + if(InventoryUtils.hasToolbarInventory(entity)){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(entity); + int i = 0; + for(Entity itemEnt : toolbarInventory.getItems()){ + if(itemEnt == null){ + freeToolbarSlot = i; + break; + } + i++; + } + } + + //check if this item type is in the natural inventory, if it is, try to equip it + if(InventoryUtils.hasNaturalInventory(entity)){ + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(entity); + //find matching natural item + Entity naturalItem = null; + for(Entity itemEnt : naturalInventory.getItems()){ + String type = CommonEntityUtils.getEntitySubtype(itemEnt); + if(type.equals(targetItemType)){ + naturalItem = itemEnt; + break; + } + } + if(naturalItem != null){ + serverToolbarState.attemptEquip(naturalItem, freeToolbarSlot); + 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 index afb21203..e99308ea 100644 --- a/src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/checks/inventory/InventoryContainsNode.java @@ -5,8 +5,6 @@ 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; @@ -33,11 +31,6 @@ public class InventoryContainsNode implements AITreeNode { @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; @@ -46,31 +39,11 @@ public class InventoryContainsNode implements AITreeNode { //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; - } + List items = InventoryUtils.getAllInventoryItems(entity); + List itemIds = items.stream().map((Entity itemEnt) -> {return CommonEntityUtils.getEntitySubtype(itemEnt);}).collect(Collectors.toList()); + if(itemIds.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; } diff --git a/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java index c386ab2b..c0eb520e 100644 --- a/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/checks/spatial/TargetRangeCheckNode.java @@ -37,7 +37,7 @@ public class TargetRangeCheckNode implements AITreeNode { 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; diff --git a/src/main/java/electrosphere/server/ai/nodes/meta/DataStorageNode.java b/src/main/java/electrosphere/server/ai/nodes/meta/DataStorageNode.java new file mode 100644 index 00000000..260c0732 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/meta/DataStorageNode.java @@ -0,0 +1,38 @@ +package electrosphere.server.ai.nodes.meta; + +import electrosphere.entity.Entity; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Stores a piece of data into a blackboard + */ +public class DataStorageNode implements AITreeNode { + + /** + * The data to store in the key + */ + Object data; + + /** + * The key to push data into + */ + String destinationKey; + + /** + * Constructor + * @param destinationKey The key to push data into + * @param data The data to store at the key + */ + public DataStorageNode(String destinationKey, Object data){ + this.data = data; + this.destinationKey = destinationKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) { + blackboard.put(this.destinationKey, this.data); + return AITreeNodeResult.SUCCESS; + } + +} diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java index 443f34d0..61b58b7f 100644 --- a/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/plan/SolveSourcingTreeNode.java @@ -44,6 +44,8 @@ public class SolveSourcingTreeNode implements AITreeNode { //set the type to harvest if this is a harvest type if(sourcingData.getHarvestTargets().size() > 0){ HarvestNode.setHarvestTargetType(blackboard, sourcingData.getHarvestTargets().get(0).getId()); + } else if(sourcingData.getTrees().size() > 0){ + HarvestNode.setHarvestTargetType(blackboard, sourcingData.getTrees().get(0).getId()); } SolveSourcingTreeNode.setItemSourcingData(blackboard, sourcingData); SolveSourcingTreeNode.setItemTargetCategory(blackboard, sourcingData.getGoalItem()); diff --git a/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java index 6ba071bb..8b76f57c 100644 --- a/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java +++ b/src/main/java/electrosphere/server/ai/trees/creature/AcquireItemTree.java @@ -15,6 +15,7 @@ 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; +import electrosphere.server.ai.trees.creature.melee.FellTree; /** * A tree to acquire an item @@ -67,7 +68,8 @@ public class AcquireItemTree { new PublishStatusNode("Fell a tree"), //check if we should be sourcing this from felling a tree new SourcingTypeNode(SourcingType.TREE, blackboardKey), - //TODO: logic to fell a tree + new TargetEntityCategoryNode(BlackboardKeys.HARVEST_TARGET_TYPE), + FellTree.create(BlackboardKeys.ENTITY_TARGET), new RunnerNode(null) ) ), diff --git a/src/main/java/electrosphere/server/ai/trees/creature/inventory/EquipToolbarTree.java b/src/main/java/electrosphere/server/ai/trees/creature/inventory/EquipToolbarTree.java new file mode 100644 index 00000000..cdf59de1 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/trees/creature/inventory/EquipToolbarTree.java @@ -0,0 +1,38 @@ +package electrosphere.server.ai.trees.creature.inventory; + +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.actions.inventory.EquipToolbarNode; +import electrosphere.server.ai.nodes.checks.inventory.InventoryContainsNode; +import electrosphere.server.ai.nodes.meta.DataTransferNode; +import electrosphere.server.ai.nodes.meta.collections.SequenceNode; +import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; + +/** + * Tries to equip an item into the toolbar + */ +public class EquipToolbarTree { + + /** + * Name of the tree + */ + public static final String TREE_NAME = "EquipToolbar"; + + /** + * Creates an equip toolbar tree + * @param itemType The type of item to try to equip in the toolbar + * @return The root node of the tree + */ + public static AITreeNode create(String targetKey){ + return new SequenceNode( + new PublishStatusNode("Equip an item"), + //check that we have this type of item + new DataTransferNode(targetKey, BlackboardKeys.INVENTORY_CHECK_TYPE), + new InventoryContainsNode(BlackboardKeys.INVENTORY_CHECK_TYPE), + + //try to equip the item to the toolbar + new EquipToolbarNode(BlackboardKeys.INVENTORY_CHECK_TYPE) + ); + } + +} diff --git a/src/main/java/electrosphere/server/ai/trees/creature/melee/FellTree.java b/src/main/java/electrosphere/server/ai/trees/creature/melee/FellTree.java new file mode 100644 index 00000000..09ba3132 --- /dev/null +++ b/src/main/java/electrosphere/server/ai/trees/creature/melee/FellTree.java @@ -0,0 +1,79 @@ +package electrosphere.server.ai.trees.creature.melee; + +import electrosphere.game.data.item.ItemIdStrings; +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; +import electrosphere.server.ai.nodes.actions.move.FaceTargetNode; +import electrosphere.server.ai.nodes.actions.move.MoveStopNode; +import electrosphere.server.ai.nodes.checks.IsMovingNode; +import electrosphere.server.ai.nodes.checks.spatial.TargetRangeCheckNode; +import electrosphere.server.ai.nodes.meta.DataStorageNode; +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.TimerNode; +import electrosphere.server.ai.nodes.meta.decorators.UntilNode; +import electrosphere.server.ai.trees.creature.MoveToTarget; +import electrosphere.server.ai.trees.creature.inventory.EquipToolbarTree; + +/** + * A behavior tree to fell a tree entity + */ +public class FellTree { + + /** + * Name of the tree + */ + public static final String TREE_NAME = "Fell"; + + /** + * Distance to start attacking at + */ + static final float FELL_RANGE = 0.5f; + + /** + * Creates a fell tree + * @param targetKey The key to lookup the target entity under + * @return The root node of the tree + */ + public static AITreeNode create(String targetKey){ + + return new SequenceNode( + //preconditions here + new DataStorageNode(BlackboardKeys.INVENTORY_CHECK_TYPE, ItemIdStrings.ITEM_STONE_AXE), + EquipToolbarTree.create(BlackboardKeys.INVENTORY_CHECK_TYPE), + + //perform different actions based on distance to target + new SelectorNode( + + //in attack range + new SequenceNode( + //check if in range of target + new TargetRangeCheckNode(FellTree.FELL_RANGE, targetKey), + //stop walking now that we're in range + new PublishStatusNode("Slowing down"), + new MoveStopNode(), + new UntilNode(AITreeNodeResult.FAILURE, new IsMovingNode()), + + //attack + new SequenceNode( + new PublishStatusNode("Attacking"), + new FaceTargetNode(BlackboardKeys.ENTITY_TARGET), + new AttackStartNode(), + new TimerNode(new RunnerNode(null), 300) + ) + ), + + //move to target + MoveToTarget.create(FellTree.FELL_RANGE, targetKey), + + //movement succeeded, but failed to attack -- tree is currently running + new RunnerNode(null) + ) + ); + } + +}