macro pathfinding scaffolding
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2025-05-30 18:05:40 -04:00
parent 6883e75874
commit 0d01a4c214
9 changed files with 289 additions and 3 deletions

View File

@ -2084,6 +2084,7 @@ Remove potential collision engine footgun
Synchronized time-of-day between server and client
Skybox reflects time of day
Standardize data sourcing in MacroTemporalData
Macro pathfinding scaffolding

View File

@ -45,6 +45,11 @@ public class BlackboardKeys {
*/
public static final String BUILDING_MATERIAL_CURRENT = "buildingMaterialCurrent";
/**
* The macro object that is being targeted
*/
public static final String MACRO_TARGET = "macroTarget";
/**
* The type of item to try to acquire
*/

View File

@ -6,7 +6,7 @@ import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.server.ai.blackboard.Blackboard;
import electrosphere.server.ai.nodes.AITreeNode;
import electrosphere.server.macro.structure.VirtualStructure;
import electrosphere.server.macro.spatial.MacroObject;
/**
* Checks if the target is inside a given range of the entity
@ -44,8 +44,8 @@ public class TargetRangeCheckNode implements AITreeNode {
targetPos = (Vector3d)targetRaw;
} else if(targetRaw instanceof Entity){
targetPos = EntityUtils.getPosition((Entity)targetRaw);
} else if(targetRaw instanceof VirtualStructure){
targetPos = ((VirtualStructure)targetRaw).getPos();
} else if(targetRaw instanceof MacroObject macroObject){
targetPos = macroObject.getPos();
} else {
throw new Error("Unsupported target type " + targetRaw);
}

View File

@ -12,6 +12,8 @@ import electrosphere.server.ai.nodes.checks.spatial.BeginStructureNode;
import electrosphere.server.macro.character.Character;
import electrosphere.server.macro.character.goal.CharacterGoal;
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
import electrosphere.server.macro.region.MacroRegion;
import electrosphere.server.macro.spatial.MacroObject;
import electrosphere.server.macro.structure.VirtualStructure;
/**
@ -84,6 +86,13 @@ public class MacroCharacterGoalNode implements AITreeNode {
}
blackboard.put(BlackboardKeys.GOAL_ITEM_ACQUISITION_TARGET, targetRaw);
} break;
case MOVE_TO_MACRO_STRUCT: {
Object targetRaw = goal.getTarget();
if(!(targetRaw instanceof VirtualStructure) && !(targetRaw instanceof MacroRegion)){
throw new Error("Unsupported type! " + targetRaw);
}
MacroCharacterGoalNode.setMacroTarget(blackboard, (MacroObject)goal.getTarget());
} break;
}
if(type == goal.getType()){
return AITreeNodeResult.SUCCESS;
@ -91,4 +100,30 @@ public class MacroCharacterGoalNode implements AITreeNode {
return AITreeNodeResult.FAILURE;
}
/**
* Sets the macro object target for the entity
* @param blackboard The blackboard
* @param object The macro object to target
*/
public static void setMacroTarget(Blackboard blackboard, MacroObject object){
blackboard.put(BlackboardKeys.MACRO_TARGET, object);
}
/**
* Checks if the blackboard has a object target
* @param blackboard The blackboard
*/
public static boolean hasMacroTarget(Blackboard blackboard){
return blackboard.has(BlackboardKeys.MACRO_TARGET);
}
/**
* Gets the object target in the blackboard
* @param blackboard The blackboard
* @return The object if it exists, null otherwise
*/
public static MacroObject getMacroTarget(Blackboard blackboard){
return (MacroObject)blackboard.get(BlackboardKeys.MACRO_TARGET);
}
}

View File

@ -0,0 +1,155 @@
package electrosphere.server.ai.nodes.plan;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.server.ai.AI;
import electrosphere.server.ai.blackboard.Blackboard;
import electrosphere.server.ai.nodes.AITreeNode;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.interfaces.PathfindingManager;
import electrosphere.server.macro.spatial.path.MacroPathNode;
import electrosphere.server.macro.structure.VirtualStructure;
import electrosphere.server.pathfinding.recast.PathingProgressiveData;
/**
* A node that uses macro pathfinding structures to accelerate calculating the pathfinding
*/
public class MacroPathfindingNode implements AITreeNode {
/**
* The value used to check if the entity is close to a pathing point horizontally
*/
public static final double CLOSENESS_CHECK_BOUND_HORIZONTAL = 0.3f;
/**
* The value used to check if the entity is close to a pathing point vertically
*/
public static final double CLOSENESS_CHECK_BOUND_VERTICAL = 0.7f;
/**
* The blackboard key to lookup the target entity under
*/
private String targetEntityKey;
/**
*
* @param targetEntityKey
*/
public static PathfindingNode createPathEntity(String targetEntityKey){
PathfindingNode rVal = new PathfindingNode();
rVal.targetEntityKey = targetEntityKey;
return rVal;
}
@Override
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){
//make sure that the solved pathfinding data is for the point we want
if(PathfindingNode.hasPathfindingData(blackboard)){
PathingProgressiveData pathingProgressiveData = PathfindingNode.getPathfindingData(blackboard);
Vector3d actualPoint = pathingProgressiveData.getGoal();
Object targetRaw = blackboard.get(this.targetEntityKey);
Vector3d targetPos = null;
if(targetRaw == null){
throw new Error("Target undefined!");
}
if(targetRaw instanceof MacroPathNode macroNode){
targetPos = macroNode.getPosition();
} else {
throw new Error("Unsupported target type " + targetRaw);
}
if(actualPoint.distance(targetPos) > CLOSENESS_CHECK_BOUND_HORIZONTAL){
PathfindingNode.clearPathfindingData(blackboard);
PathfindingNode.clearPathfindingPoint(blackboard);
}
}
//create a path if we don't already have one
if(!PathfindingNode.hasPathfindingData(blackboard)){
Object targetRaw = blackboard.get(this.targetEntityKey);
Vector3d targetPos = null;
if(targetRaw == null){
throw new Error("Target undefined!");
}
if(targetRaw instanceof Vector3d){
targetPos = (Vector3d)targetRaw;
} else if(targetRaw instanceof Entity){
targetPos = EntityUtils.getPosition((Entity)targetRaw);
} else if(targetRaw instanceof VirtualStructure){
targetPos = ((VirtualStructure)targetRaw).getPos();
} else {
throw new Error("Unsupported target type " + targetRaw);
}
Realm realm = Globals.serverState.realmManager.getEntityRealm(entity);
PathfindingManager pathfindingManager = realm.getPathfindingManager();
Vector3d entityPos = EntityUtils.getPosition(entity);
PathingProgressiveData pathingProgressiveData = pathfindingManager.findPathAsync(entityPos, targetPos);
PathfindingNode.setPathfindingData(blackboard, pathingProgressiveData);
}
if(!PathfindingNode.hasPathfindingData(blackboard)){
throw new Error("Failed to find path! Unhandled");
}
//check if the path has been found
PathingProgressiveData pathingProgressiveData = PathfindingNode.getPathfindingData(blackboard);
if(!pathingProgressiveData.isReady()){
AI.getAI(entity).setStatus("Thinking about pathing");
return AITreeNodeResult.RUNNING;
}
Vector3d entityPos = EntityUtils.getPosition(entity);
Vector3d currentPathPos = null;
if(pathingProgressiveData.getCurrentPoint() < pathingProgressiveData.getPoints().size()){
currentPathPos = pathingProgressiveData.getPoints().get(pathingProgressiveData.getCurrentPoint());
}
double vertDist = Math.abs(currentPathPos.y - entityPos.y);
double horizontalDist = Math.sqrt((currentPathPos.x - entityPos.x) * (currentPathPos.x - entityPos.x) + (currentPathPos.z - entityPos.z) * (currentPathPos.z - entityPos.z));
while(
currentPathPos != null &&
vertDist < CLOSENESS_CHECK_BOUND_VERTICAL &&
horizontalDist < CLOSENESS_CHECK_BOUND_HORIZONTAL &&
pathingProgressiveData.getCurrentPoint() < pathingProgressiveData.getPoints().size() - 1
){
pathingProgressiveData.setCurrentPoint(pathingProgressiveData.getCurrentPoint() + 1);
currentPathPos = pathingProgressiveData.getPoints().get(pathingProgressiveData.getCurrentPoint());
}
//if we're close enough to the final pathing point, always path to actual final point
if(
vertDist < CLOSENESS_CHECK_BOUND_VERTICAL &&
horizontalDist < CLOSENESS_CHECK_BOUND_HORIZONTAL &&
pathingProgressiveData.getCurrentPoint() == pathingProgressiveData.getPoints().size() - 1
){
Object targetRaw = blackboard.get(this.targetEntityKey);
Vector3d targetPos = null;
if(targetRaw == null){
throw new Error("Target undefined!");
}
if(targetRaw instanceof Vector3d){
targetPos = (Vector3d)targetRaw;
} else if(targetRaw instanceof Entity){
targetPos = EntityUtils.getPosition((Entity)targetRaw);
} else if(targetRaw instanceof VirtualStructure){
targetPos = ((VirtualStructure)targetRaw).getPos();
} else {
throw new Error("Unsupported target type " + targetRaw);
}
currentPathPos = targetPos;
}
PathfindingNode.setPathfindingPoint(blackboard, currentPathPos);
return AITreeNodeResult.SUCCESS;
}
}

View File

@ -11,6 +11,7 @@ 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.trees.creature.MacroMoveToTree;
import electrosphere.server.ai.trees.creature.resource.AcquireItemTree;
import electrosphere.server.ai.trees.struct.BuildStructureTree;
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
@ -53,6 +54,13 @@ public class CharacterGoalTree {
new PublishStatusNode("Acquire building material"),
//try to find building materials
AcquireItemTree.create(BlackboardKeys.GOAL_ITEM_ACQUISITION_TARGET)
),
//character's goal is to move to a macro virtual structure
new SequenceNode(
MacroCharacterGoalNode.create(CharacterGoalType.MOVE_TO_MACRO_STRUCT),
new PublishStatusNode("Move to macro structure"),
MacroMoveToTree.create(BlackboardKeys.MACRO_TARGET)
)
)
);

View File

@ -0,0 +1,69 @@
package electrosphere.server.ai.trees.creature;
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
import electrosphere.server.ai.blackboard.BlackboardKeys;
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.TargetRangeCheckNode;
import electrosphere.server.ai.nodes.meta.DataDeleteNode;
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.PathfindingNode;
/**
* Tree that moves an entity to a macro structure
*/
public class MacroMoveToTree {
/**
* Name of the tree
*/
public static final String TREE_NAME = "MacroMoveTo";
/**
* Default distance to be within
*/
static final double DEFAULT_DIST = 0.5f;
/**
* Creates a move-to-target tree
* @param targetKey The key to lookup the target under
* @return The root node of the move-to-target tree
*/
public static AITreeNode create(String targetKey){
return MacroMoveToTree.create(DEFAULT_DIST, targetKey);
}
/**
* 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, String targetKey){
if(dist < PathfindingNode.CLOSENESS_CHECK_BOUND_HORIZONTAL){
throw new Error("Dist less than minimal amount! " + dist);
}
return new SelectorNode(
new SequenceNode(
//check if in range of target
new TargetRangeCheckNode(dist, targetKey),
new DataDeleteNode(BlackboardKeys.PATHFINDING_POINT),
new DataDeleteNode(BlackboardKeys.PATHFINDING_DATA),
//if in range, stop moving fowards and return SUCCESS
new SucceederNode(new MoveStopNode())
),
//not in range of target, keep moving towards it
new SequenceNode(
PathfindingNode.createPathEntity(targetKey),
new FaceTargetNode(BlackboardKeys.PATHFINDING_POINT),
new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD))
)
);
}
}

View File

@ -26,6 +26,10 @@ public class CharacterGoal extends CharacterData {
* Acquire an item
*/
ACQUIRE_ITEM,
/**
* Move to a macro structure
*/
MOVE_TO_MACRO_STRUCT,
}
/**

View File

@ -13,6 +13,7 @@ import electrosphere.server.macro.character.data.CharacterDataStrings;
import electrosphere.server.macro.character.goal.CharacterGoal;
import electrosphere.server.macro.character.goal.CharacterGoal.CharacterGoalType;
import electrosphere.server.macro.civilization.town.Town;
import electrosphere.server.macro.region.MacroRegion;
import electrosphere.server.macro.structure.VirtualStructure;
import electrosphere.server.macro.utils.StructurePlacementUtils;
import electrosphere.server.macro.utils.StructureRepairUtils;
@ -40,6 +41,14 @@ public class CharaSimulation {
if(CharaSimulation.checkTownGoals(realm, chara)){
return;
}
//send a character on a walk
// if(chara.getId() == 3){
// Town hometown = CharacterUtils.getHometown(realm.getMacroData(), chara);
// if(hometown != null){
// MacroRegion target = hometown.getFarmPlots(realm.getMacroData()).get(0);
// CharacterGoal.setCharacterGoal(chara, new CharacterGoal(CharacterGoalType.MOVE_TO_MACRO_STRUCT, target));
// }
// }
}
/**