debug ability to send characters off map
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2025-05-10 12:38:35 -04:00
parent 51f9cde57f
commit 3b46a3ebe9
16 changed files with 303 additions and 3 deletions

View File

@ -596,7 +596,7 @@
},
"aiTrees" : [
{
"name" : "Maslow"
"name" : "StandardCharacter"
}
],
"cameraData" : {

View File

@ -0,0 +1,5 @@
@page ai AI
Architecture pages around AI
[TOC]
- @subpage btreeorganization

View 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

View File

@ -22,6 +22,7 @@
- @subpage worldgenerationindex
- @subpage chemistryindex
- @subpage terrainindex
- @subpage ai
# What is this section

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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