debug ability to send characters off map
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
51f9cde57f
commit
3b46a3ebe9
@ -596,7 +596,7 @@
|
||||
},
|
||||
"aiTrees" : [
|
||||
{
|
||||
"name" : "Maslow"
|
||||
"name" : "StandardCharacter"
|
||||
}
|
||||
],
|
||||
"cameraData" : {
|
||||
|
||||
5
docs/src/architecture/ai/ai.md
Normal file
5
docs/src/architecture/ai/ai.md
Normal file
@ -0,0 +1,5 @@
|
||||
@page ai AI
|
||||
Architecture pages around AI
|
||||
|
||||
[TOC]
|
||||
- @subpage btreeorganization
|
||||
12
docs/src/architecture/ai/btreeorganization.md
Normal file
12
docs/src/architecture/ai/btreeorganization.md
Normal file
@ -0,0 +1,12 @@
|
||||
@page btreeorganization Behavior Tree Organization
|
||||
|
||||
|
||||
General design flow when thinking about behavior trees
|
||||
|
||||
1. Sequence node that checks initial conditions for this tree to activate at all
|
||||
2. Selector node that selects between the different "phases", "goals", whatever you want to call them
|
||||
3. Generally want to flow like:
|
||||
4. Check conditions for this phase
|
||||
5. If conditions pass, set variables to perform actions
|
||||
6. Perform actions
|
||||
7. If this phase is an action that we want to represent as ongoing, return a Runner node at the end of the phase
|
||||
@ -22,6 +22,7 @@
|
||||
- @subpage worldgenerationindex
|
||||
- @subpage chemistryindex
|
||||
- @subpage terrainindex
|
||||
- @subpage ai
|
||||
|
||||
|
||||
# What is this section
|
||||
|
||||
@ -1696,6 +1696,10 @@ Placing fabs/blocks triggers structure scanning service
|
||||
Try closing ai manager threads on engine close
|
||||
Ability to explicitly spawn new characters into macro data and have them then spawn into world
|
||||
|
||||
(05/10/2025)
|
||||
Debug ability to send characters off map
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import electrosphere.entity.ClientEntityUtils;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityCreationUtils;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.server.ServerCharacterData;
|
||||
import electrosphere.renderer.actor.Actor;
|
||||
import electrosphere.renderer.actor.ActorTextureMask;
|
||||
import electrosphere.renderer.ui.imgui.ImGuiWindow;
|
||||
@ -22,7 +23,11 @@ import electrosphere.server.ai.nodes.plan.PathfindingNode;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.gridded.GriddedDataCellManager;
|
||||
import electrosphere.server.datacell.utils.EntityLookupUtils;
|
||||
import electrosphere.server.macro.MacroData;
|
||||
import electrosphere.server.macro.character.Character;
|
||||
import electrosphere.server.macro.character.CharacterUtils;
|
||||
import electrosphere.server.macro.character.goal.CharacterGoal;
|
||||
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
|
||||
import electrosphere.server.pathfinding.voxel.VoxelPathfinder;
|
||||
import electrosphere.server.pathfinding.voxel.VoxelPathfinder.PathfinderNode;
|
||||
import imgui.ImGui;
|
||||
@ -85,7 +90,11 @@ public class ImGuiAI {
|
||||
throw new Error("Unsupported currently!");
|
||||
}
|
||||
if(ImGui.button("Send off map")){
|
||||
throw new Error("Unsupported currently!");
|
||||
Entity entity = ai.getParent();
|
||||
ServerCharacterData serverCharacterData = ServerCharacterData.getServerCharacterData(entity);
|
||||
MacroData macroData = Globals.realmManager.getEntityRealm(entity).getServerContentManager().getMacroData();
|
||||
Character character = macroData.getCharacter(serverCharacterData.getCharacterId());
|
||||
CharacterGoal.setCharacterGoal(character, new CharacterGoal(CharacterGoalType.LEAVE_SIM_RANGE));
|
||||
}
|
||||
}
|
||||
ImGui.unindent();
|
||||
|
||||
@ -9,6 +9,7 @@ import com.google.gson.JsonParseException;
|
||||
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||
import electrosphere.server.ai.trees.character.StandardCharacterTree;
|
||||
import electrosphere.server.ai.trees.creature.AttackerAITree;
|
||||
import electrosphere.server.ai.trees.hierarchy.MaslowTree;
|
||||
|
||||
@ -30,6 +31,9 @@ public class AITreeDataSerializer implements JsonDeserializer<AITreeData> {
|
||||
case MaslowTree.TREE_NAME: {
|
||||
return context.deserialize(json, MaslowTreeData.class);
|
||||
}
|
||||
case StandardCharacterTree.TREE_NAME: {
|
||||
return context.deserialize(json, StandardCharacterTreeData.class);
|
||||
}
|
||||
}
|
||||
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("JSON Object provided to AITreeDataSerializer that cannot deserialize into a tree data type cleanly " + json.getAsJsonObject().get("name").getAsString()));
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
package electrosphere.game.data.creature.type.ai;
|
||||
|
||||
import electrosphere.server.ai.trees.character.StandardCharacterTree;
|
||||
|
||||
/**
|
||||
* Tree data for controlling a standard character tree
|
||||
*/
|
||||
public class StandardCharacterTreeData implements AITreeData {
|
||||
|
||||
/**
|
||||
* The name of the tree
|
||||
*/
|
||||
String name;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StandardCharacterTree.TREE_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,9 +10,11 @@ import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.game.data.creature.type.ai.AITreeData;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.game.data.creature.type.ai.BlockerTreeData;
|
||||
import electrosphere.game.data.creature.type.ai.StandardCharacterTreeData;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.trees.character.StandardCharacterTree;
|
||||
import electrosphere.server.ai.trees.creature.AttackerAITree;
|
||||
import electrosphere.server.ai.trees.hierarchy.MaslowTree;
|
||||
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||
@ -78,6 +80,9 @@ public class AI {
|
||||
case MaslowTree.TREE_NAME: {
|
||||
rVal.rootNode = MaslowTree.create();
|
||||
} break;
|
||||
case StandardCharacterTree.TREE_NAME: {
|
||||
rVal.rootNode = StandardCharacterTree.create((StandardCharacterTreeData) aiData);
|
||||
} break;
|
||||
default: {
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName()));
|
||||
} break;
|
||||
|
||||
@ -80,4 +80,9 @@ public class BlackboardKeys {
|
||||
*/
|
||||
public static final String PATHFINDING_POINT = "pathfindingPoint";
|
||||
|
||||
/**
|
||||
* A point that the entity is targeting
|
||||
*/
|
||||
public static final String POINT_TARGET = "pointTarget";
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
package electrosphere.server.ai.nodes.macro;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.server.ServerCharacterData;
|
||||
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.datacell.gridded.GriddedDataCellManager;
|
||||
import electrosphere.server.macro.character.Character;
|
||||
import electrosphere.server.macro.MacroData;
|
||||
import electrosphere.server.macro.character.goal.CharacterGoal;
|
||||
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
|
||||
|
||||
/**
|
||||
* Node for interacting with macro character goals
|
||||
*/
|
||||
public class MacroCharacterGoalNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The type to check for
|
||||
*/
|
||||
CharacterGoalType type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param destinationKey The key to push data into
|
||||
*/
|
||||
public static MacroCharacterGoalNode create(CharacterGoalType type){
|
||||
MacroCharacterGoalNode rVal = new MacroCharacterGoalNode();
|
||||
rVal.type = type;
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any goal being present
|
||||
*/
|
||||
public static MacroCharacterGoalNode createAny(){
|
||||
MacroCharacterGoalNode rVal = new MacroCharacterGoalNode();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(!ServerCharacterData.hasServerCharacterDataTree(entity)){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
ServerCharacterData serverCharacterData = ServerCharacterData.getServerCharacterData(entity);
|
||||
MacroData macroData = Globals.realmManager.getEntityRealm(entity).getServerContentManager().getMacroData();
|
||||
Character character = macroData.getCharacter(serverCharacterData.getCharacterId());
|
||||
CharacterGoal goal = CharacterGoal.getCharacterGoal(character);
|
||||
if(goal == null){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
//this is the conditional for the any-type goal
|
||||
if(type == null && goal != null){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
//fail fast if the goal does not align with the type this node was initialized with
|
||||
if(type != goal.getType()){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
switch(goal.getType()){
|
||||
case LEAVE_SIM_RANGE: {
|
||||
Realm realm = Globals.realmManager.getEntityRealm(entity);
|
||||
Vector3d entityPos = EntityUtils.getPosition(entity);
|
||||
Vector3d offset = new Vector3d(entityPos).add(1000,0,0);
|
||||
GriddedDataCellManager griddedDataCellManager = (GriddedDataCellManager)realm.getDataCellManager();
|
||||
Vector3d targetPos = griddedDataCellManager.getMacroEntryPoint(offset);
|
||||
blackboard.put(BlackboardKeys.POINT_TARGET, targetPos);
|
||||
} break;
|
||||
}
|
||||
if(type == goal.getType()){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -28,7 +28,7 @@ public class PathfindingNode implements AITreeNode {
|
||||
/**
|
||||
* The value used to check if the entity is close to a pathing point vertically
|
||||
*/
|
||||
public static final double CLOSENESS_CHECK_BOUND_VERTICAL = 0.5f;
|
||||
public static final double CLOSENESS_CHECK_BOUND_VERTICAL = 0.7f;
|
||||
|
||||
/**
|
||||
* The blackboard key to lookup the target entity under
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package electrosphere.server.ai.trees.character;
|
||||
|
||||
import electrosphere.game.data.creature.type.ai.StandardCharacterTreeData;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
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.trees.character.goals.CharacterGoalTree;
|
||||
import electrosphere.server.ai.trees.hierarchy.MaslowTree;
|
||||
|
||||
/**
|
||||
* Standard behavior tree for macro characters
|
||||
*/
|
||||
public class StandardCharacterTree {
|
||||
|
||||
/**
|
||||
* Name of the tree
|
||||
*/
|
||||
public static final String TREE_NAME = "StandardCharacter";
|
||||
|
||||
/**
|
||||
* Creates a standard character tree
|
||||
* @param data The data for the tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(StandardCharacterTreeData data){
|
||||
return new SequenceNode(
|
||||
new PublishStatusNode("StandardCharacter"),
|
||||
//check that dependencies exist
|
||||
new SelectorNode(
|
||||
CharacterGoalTree.create(),
|
||||
MaslowTree.create()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package electrosphere.server.ai.trees.character.goals;
|
||||
|
||||
import electrosphere.server.ai.blackboard.BlackboardKeys;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.macro.MacroCharacterGoalNode;
|
||||
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.trees.creature.MoveToTree;
|
||||
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
|
||||
|
||||
/**
|
||||
* Performs macro-character-defined goals
|
||||
*/
|
||||
public class CharacterGoalTree {
|
||||
|
||||
/**
|
||||
* Creates a character goal tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(){
|
||||
return new SequenceNode(
|
||||
//check if we have goals
|
||||
MacroCharacterGoalNode.createAny(),
|
||||
//select based on the type of goals the character has
|
||||
new SelectorNode(
|
||||
|
||||
//character's goal is to leave sim range
|
||||
new SequenceNode(
|
||||
//check if we're trying to leave sim range
|
||||
MacroCharacterGoalNode.create(CharacterGoalType.LEAVE_SIM_RANGE),
|
||||
new PublishStatusNode("Leaving simulation range"),
|
||||
MoveToTree.create(BlackboardKeys.POINT_TARGET)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -14,5 +14,10 @@ public class CharacterDataStrings {
|
||||
public static final String STRUCTURE = "structure";
|
||||
public static final String HOMETOWN = "hometown";
|
||||
public static final String TOWN = "town";
|
||||
|
||||
/**
|
||||
* Goal for a given entity
|
||||
*/
|
||||
public static final String ENTITY_GOAL = "entityGoal";
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
package electrosphere.server.macro.character.goal;
|
||||
|
||||
import electrosphere.server.macro.character.Character;
|
||||
import electrosphere.server.macro.character.data.CharacterData;
|
||||
import electrosphere.server.macro.character.data.CharacterDataStrings;
|
||||
|
||||
/**
|
||||
* Utilities for working with goals on macro characters
|
||||
*/
|
||||
public class CharacterGoal extends CharacterData {
|
||||
|
||||
/**
|
||||
* A specific type of goal
|
||||
*/
|
||||
public enum CharacterGoalType {
|
||||
/**
|
||||
* Goals is generally to leave simulation range
|
||||
*/
|
||||
LEAVE_SIM_RANGE,
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of goal
|
||||
*/
|
||||
CharacterGoalType type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param type The type of goal
|
||||
*/
|
||||
public CharacterGoal(CharacterGoalType type){
|
||||
super(CharacterDataStrings.ENTITY_GOAL);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of goal that this is
|
||||
* @return The type
|
||||
*/
|
||||
public CharacterGoalType getType(){
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the goal on a character
|
||||
* @param character The character
|
||||
* @param goal The goal
|
||||
*/
|
||||
public static void setCharacterGoal(Character character, CharacterGoal goal){
|
||||
character.putData(CharacterDataStrings.ENTITY_GOAL, goal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the goal on a character
|
||||
* @param character The character
|
||||
* @return true if the character has a goal, false otherwise
|
||||
*/
|
||||
public static boolean hasCharacterGoal(Character character){
|
||||
return character.containsKey(CharacterDataStrings.ENTITY_GOAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the goal of the character
|
||||
* @param character The character
|
||||
* @return The goal if it exists, null otherwise
|
||||
*/
|
||||
public static CharacterGoal getCharacterGoal(Character character){
|
||||
return (CharacterGoal)character.getData(CharacterDataStrings.ENTITY_GOAL);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user