true behavior trees
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
This commit is contained in:
parent
bdec668fb8
commit
61c5599265
@ -533,7 +533,9 @@
|
||||
},
|
||||
"aiTrees" : [
|
||||
{
|
||||
"name" : "Blocker"
|
||||
"name" : "Attacker",
|
||||
"aggroRange" : 10,
|
||||
"attackRange" : 0.8
|
||||
}
|
||||
],
|
||||
"boneGroups" : [
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
+ when you grab the sword, a tutorial popup appears to tell you how to use in
|
||||
+ on clearing the tutorial, continue the game when the sword is equipped, create another popup to teach sword controls. it pauses the game
|
||||
+ when popup is accepted, spawn an enemy with an effect
|
||||
enemy ai
|
||||
review combat code (lifestate, damage calculation, etc)
|
||||
Maybe a fade-out before deleting entity on death?
|
||||
|
||||
@ -15,6 +14,9 @@
|
||||
+ fix the vibes
|
||||
Stability
|
||||
Movement penalty while swinging weapon
|
||||
Only have ai strafe if its outside attack range
|
||||
Ticketed randomizer node for BTs to more heavily weight attacking and waiting
|
||||
Slow down strafe movement somehow
|
||||
|
||||
+ bug fixes
|
||||
Fix grass rendering distance
|
||||
|
||||
@ -566,6 +566,15 @@ Fix deleting entities on server not properly deleting the whole entity
|
||||
Windup and Cooldown animations on 2h block
|
||||
AI rearchitecture to actually do behavior trees now that I know what they are
|
||||
|
||||
(08/15/2024)
|
||||
True behavior trees
|
||||
- Decorators
|
||||
- BT Timer service
|
||||
- Meta nodes
|
||||
- Colections
|
||||
- Combat
|
||||
Melee ai using BT framework
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
@ -431,7 +431,7 @@ public class Globals {
|
||||
//script engine
|
||||
scriptEngine = new ScriptEngine();
|
||||
//ai manager
|
||||
aiManager = new AIManager();
|
||||
aiManager = new AIManager(0);
|
||||
//realm & data cell manager
|
||||
realmManager = new RealmManager();
|
||||
entityDataCellMapper = new EntityDataCellMapper();
|
||||
|
||||
@ -511,6 +511,18 @@ public class ServerAttackTree implements BehaviorTree {
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this tree can start an attack
|
||||
* @return true if can attack, false otherwise
|
||||
*/
|
||||
public boolean canAttack(){
|
||||
String attackType = getAttackType();
|
||||
if(attackType == null){
|
||||
return false;
|
||||
}
|
||||
return this.canAttack(attackType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next attack move
|
||||
* @param moveset The moveset
|
||||
@ -570,6 +582,15 @@ public class ServerAttackTree implements BehaviorTree {
|
||||
this.collidedEntities.add(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entity has a copy of this tree
|
||||
* @param target The entity
|
||||
* @return true if has a copy of this tree, false otherwise
|
||||
*/
|
||||
public static boolean hasAttackTree(Entity target){
|
||||
return target.containsKey(EntityDataStrings.TREE_SERVERATTACKTREE);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
|
||||
@ -431,6 +431,18 @@ public class ServerGroundMovementTree implements BehaviorTree {
|
||||
networkMessageQueue.add(networkMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tree is moving
|
||||
* @return true if is moving, false otherwise
|
||||
*/
|
||||
public boolean isMoving(){
|
||||
return this.state != MovementTreeState.IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tree CAN start moving
|
||||
* @return true if CAN start moving, false otherwise
|
||||
*/
|
||||
public boolean canStartMoving(){
|
||||
boolean rVal = true;
|
||||
return rVal;
|
||||
|
||||
@ -11,6 +11,8 @@ import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityDataStrings;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.btree.BehaviorTree;
|
||||
import electrosphere.entity.types.camera.CameraEntityUtils;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.net.parser.net.message.EntityMessage;
|
||||
|
||||
/**
|
||||
@ -40,6 +42,8 @@ public class ClientPhysicsSyncTree implements BehaviorTree {
|
||||
public void simulate(float deltaTime) {
|
||||
if(!hasPushesMessage && latestMessage != null){
|
||||
this.hasPushesMessage = true;
|
||||
//
|
||||
//get values
|
||||
Vector3d position = new Vector3d(latestMessage.getpositionX(),latestMessage.getpositionY(),latestMessage.getpositionZ());
|
||||
Quaterniond rotationFromServer = new Quaterniond(latestMessage.getrotationX(),latestMessage.getrotationY(),latestMessage.getrotationZ(),latestMessage.getrotationW());
|
||||
Vector3d linearVelocity = new Vector3d(latestMessage.getlinVelX(), latestMessage.getlinVelY(), latestMessage.getlinVelZ());
|
||||
@ -47,9 +51,18 @@ public class ClientPhysicsSyncTree implements BehaviorTree {
|
||||
Vector3d linearForce = new Vector3d(latestMessage.getlinForceX(), latestMessage.getlinForceY(), latestMessage.getlinForceZ());
|
||||
Vector3d angularForce = new Vector3d();
|
||||
DBody body = PhysicsEntityUtils.getDBody(parent);
|
||||
|
||||
//
|
||||
//Synchronize data
|
||||
EntityUtils.getPosition(parent).set(position);
|
||||
EntityUtils.getRotation(parent).set(rotationFromServer);
|
||||
PhysicsUtils.synchronizeData(Globals.clientSceneWrapper.getCollisionEngine(), body, position, rotationFromServer, linearVelocity, angularVelocity, linearForce, angularForce);
|
||||
|
||||
//
|
||||
//update facing vector if relevant
|
||||
if(CreatureUtils.getFacingVector(parent) != null){
|
||||
CreatureUtils.setFacingVector(parent, CameraEntityUtils.getFacingVec(rotationFromServer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -297,6 +297,22 @@ public class CameraEntityUtils {
|
||||
return new Vector3d(rotationVecRaw.x,0,rotationVecRaw.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the facing vector from the quaternion
|
||||
* @param rotation The rotation to construct the facing vector with
|
||||
* @return The facing vector
|
||||
*/
|
||||
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);
|
||||
Vector4d rotationVecRaw = MathUtils.getOriginVector4();
|
||||
rotationVecRaw = rotationMat.transform(rotationVecRaw);
|
||||
if(rotationVecRaw.length() < 0.001){
|
||||
rotationVecRaw.set(MathUtils.getOriginVector4());
|
||||
}
|
||||
return new Vector3d(rotationVecRaw.x,0,rotationVecRaw.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation matrix from a yaw and pitch
|
||||
* @param yaw The yaw
|
||||
|
||||
@ -15,11 +15,6 @@ public class AttackerTreeData implements AITreeData {
|
||||
*/
|
||||
float attackRange;
|
||||
|
||||
/**
|
||||
* The number of frames to wait before changing between states
|
||||
*/
|
||||
int stateChangeTimeout;
|
||||
|
||||
/**
|
||||
* Gets the range at which this entity will locate enemies
|
||||
* @return The range
|
||||
@ -36,14 +31,6 @@ public class AttackerTreeData implements AITreeData {
|
||||
return attackRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of frames to wait before changing between states
|
||||
* @return The number of frames
|
||||
*/
|
||||
public int getStateChangeTimeout(){
|
||||
return stateChangeTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Attacker";
|
||||
|
||||
@ -112,4 +112,13 @@ public class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message at the specified logging level
|
||||
* @param level The logging level
|
||||
* @param message The message
|
||||
*/
|
||||
public void PRINT(LogLevel level, String message){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ public class LoggerInterface {
|
||||
public static Logger loggerAudio;
|
||||
public static Logger loggerUI;
|
||||
public static Logger loggerScripts;
|
||||
public static Logger loggerAI;
|
||||
|
||||
/**
|
||||
* Initializes all logic objects
|
||||
@ -36,6 +37,7 @@ public class LoggerInterface {
|
||||
loggerAudio = new Logger(LogLevel.WARNING);
|
||||
loggerUI = new Logger(LogLevel.WARNING);
|
||||
loggerScripts = new Logger(LogLevel.WARNING);
|
||||
loggerAI = new Logger(LogLevel.LOOP_DEBUG);
|
||||
loggerStartup.INFO("Initialized loggers");
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package electrosphere.menu.debug;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.renderer.ui.imgui.ImGuiWindow;
|
||||
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
|
||||
import electrosphere.server.ai.AI;
|
||||
import imgui.ImGui;
|
||||
|
||||
/**
|
||||
@ -36,6 +37,12 @@ public class ImGuiAI {
|
||||
if(ImGui.button("Toggle AI Manager")){
|
||||
Globals.aiManager.setActive(!Globals.aiManager.isActive());
|
||||
}
|
||||
|
||||
if(ImGui.collapsingHeader("Statuses")){
|
||||
for(AI ai : Globals.aiManager.getAIList()){
|
||||
ImGui.text(ai.getParent().getId() + " - " + ai.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@ -7,8 +7,12 @@ import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityDataStrings;
|
||||
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.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.trees.creature.AttackerAITree;
|
||||
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||
|
||||
/**
|
||||
@ -42,6 +46,11 @@ public class AI {
|
||||
*/
|
||||
List<AITreeNode> evaluatedNodes = new LinkedList<AITreeNode>();
|
||||
|
||||
/**
|
||||
* The status of the ai
|
||||
*/
|
||||
String status = "Idle";
|
||||
|
||||
/**
|
||||
* Constructs an AI from a list of trees that should be present on the ai
|
||||
* @param treeData The list of data on trees to be provided
|
||||
@ -56,6 +65,9 @@ public class AI {
|
||||
case BlockerAITree.TREE_NAME: {
|
||||
rVal.rootNode = BlockerAITree.create((BlockerTreeData) aiData);
|
||||
} break;
|
||||
case AttackerAITree.TREE_NAME: {
|
||||
rVal.rootNode = AttackerAITree.create((AttackerTreeData) aiData);
|
||||
} break;
|
||||
default: {
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName()));
|
||||
} break;
|
||||
@ -77,21 +89,7 @@ public class AI {
|
||||
*/
|
||||
protected void simulate(){
|
||||
if(this.shouldExecute()){
|
||||
AITreeNode currentNode = this.rootNode;
|
||||
evaluatedNodes.clear();
|
||||
while(currentNode != null){
|
||||
//
|
||||
//loop checking
|
||||
if(evaluatedNodes.contains(currentNode)){
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalStateException("AI tree looped!"));
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
//evaluate node
|
||||
evaluatedNodes.add(currentNode);
|
||||
currentNode = currentNode.evaluate(this.parent, this.blackboard);
|
||||
}
|
||||
this.rootNode.evaluate(this.parent, this.blackboard);
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,5 +138,21 @@ public class AI {
|
||||
private boolean shouldExecute(){
|
||||
return !CreatureUtils.hasControllerPlayerId(this.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of the ai
|
||||
* @param status The status
|
||||
*/
|
||||
public void setStatus(String status){
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the ai
|
||||
* @return The status
|
||||
*/
|
||||
public String getStatus(){
|
||||
return this.status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,10 +4,12 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.game.data.creature.type.ai.AITreeData;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.services.TimerService;
|
||||
|
||||
/**
|
||||
* Server manager for all entity AIs
|
||||
@ -33,18 +35,32 @@ public class AIManager {
|
||||
* Controls whether the ai manager should simulate each frame or not
|
||||
*/
|
||||
boolean active = true;
|
||||
|
||||
/**
|
||||
* The timer service
|
||||
*/
|
||||
TimerService timerService = new TimerService();
|
||||
|
||||
/**
|
||||
* The random of the ai
|
||||
*/
|
||||
Random random = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public AIManager(){
|
||||
|
||||
public AIManager(long seed){
|
||||
this.random = new Random(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates all AIs currently available
|
||||
*/
|
||||
public void simulate(){
|
||||
//exec the services
|
||||
execServices();
|
||||
|
||||
//simulate each tree
|
||||
if(isActive()){
|
||||
for(AI ai : aiList){
|
||||
ai.simulate();
|
||||
@ -89,6 +105,7 @@ public class AIManager {
|
||||
aiList.add(ai);
|
||||
entityAIMap.put(entity,ai);
|
||||
aiEntityMap.put(ai,entity);
|
||||
AI.setAI(entity, ai);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,5 +118,28 @@ public class AIManager {
|
||||
aiEntityMap.remove(targetAI);
|
||||
entityAIMap.remove(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all the services
|
||||
*/
|
||||
private void execServices(){
|
||||
timerService.exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timer service
|
||||
* @return The timer service
|
||||
*/
|
||||
public TimerService getTimerService(){
|
||||
return timerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ai manager's random
|
||||
* @return The random
|
||||
*/
|
||||
public Random getRandom(){
|
||||
return random;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
package electrosphere.server.ai;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
|
||||
/**
|
||||
* A node in a behavior tree
|
||||
*/
|
||||
public interface AITreeNode {
|
||||
|
||||
/**
|
||||
* Evaluates the node
|
||||
* @param entity The entity to evaluate this node on
|
||||
* @param blackboard The blackboard for the tree
|
||||
* @return The next node to run, or null if no more nodes should be run
|
||||
*/
|
||||
public AITreeNode evaluate(Entity entity, Blackboard blackboard);
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package electrosphere.server.ai;
|
||||
package electrosphere.server.ai.blackboard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -0,0 +1,13 @@
|
||||
package electrosphere.server.ai.blackboard;
|
||||
|
||||
/**
|
||||
* Keys for the blackboard
|
||||
*/
|
||||
public class BlackboardKeys {
|
||||
|
||||
/*
|
||||
* Melee ai related
|
||||
*/
|
||||
public static final String MELEE_TARGET = "meleeTarget";
|
||||
|
||||
}
|
||||
37
src/main/java/electrosphere/server/ai/nodes/AITreeNode.java
Normal file
37
src/main/java/electrosphere/server/ai/nodes/AITreeNode.java
Normal file
@ -0,0 +1,37 @@
|
||||
package electrosphere.server.ai.nodes;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
|
||||
/**
|
||||
* A node in a behavior tree
|
||||
*/
|
||||
public interface AITreeNode {
|
||||
|
||||
/**
|
||||
* Different results that a node can return
|
||||
*/
|
||||
public static enum AITreeNodeResult {
|
||||
/**
|
||||
* Successfully executed
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* Still running
|
||||
*/
|
||||
RUNNING,
|
||||
/**
|
||||
* Failed
|
||||
*/
|
||||
FAILURE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the node
|
||||
* @param entity The entity to evaluate this node on
|
||||
* @param blackboard The blackboard for the tree
|
||||
* @return The result of evaluating the node
|
||||
*/
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard);
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package electrosphere.server.ai.nodes.actions;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.block.ServerBlockTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Attempts to block
|
||||
*/
|
||||
public class BlockStartNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BlockStartNode(){
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package electrosphere.server.ai.nodes.actions;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.block.ServerBlockTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Tries to stop blocking
|
||||
*/
|
||||
public class BlockStopNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BlockStopNode(){
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package electrosphere.server.ai.nodes.actions.combat;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.attack.ServerAttackTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Starts an attack
|
||||
*/
|
||||
public class AttackStartNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public AttackStartNode(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(!ServerAttackTree.hasAttackTree(entity)){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
ServerAttackTree serverAttackTree = ServerAttackTree.getServerAttackTree(entity);
|
||||
if(!serverAttackTree.canAttack()){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
if(serverAttackTree.isAttacking()){
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
serverAttackTree.start();
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package electrosphere.server.ai.nodes.actions.combat;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Checks if the target is in a range type
|
||||
*/
|
||||
public class MeleeRangeCheckNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The type of check to run
|
||||
*/
|
||||
public static enum MeleeRangeCheckType {
|
||||
/**
|
||||
* Check if in aggro range
|
||||
*/
|
||||
AGGRO,
|
||||
/**
|
||||
* Check if in attack range
|
||||
*/
|
||||
ATTACK,
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of check to perform
|
||||
*/
|
||||
MeleeRangeCheckType checkType = null;
|
||||
|
||||
/**
|
||||
* The attacker tree data
|
||||
*/
|
||||
AttackerTreeData attackerTreeData = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param attackerTreeData The attacker tree data
|
||||
* @param checkType The type of check to perform
|
||||
*/
|
||||
public MeleeRangeCheckNode(AttackerTreeData attackerTreeData, MeleeRangeCheckType checkType){
|
||||
if(attackerTreeData == null){
|
||||
throw new IllegalArgumentException("Trying to create melee range check node with null tree data!");
|
||||
}
|
||||
if(checkType == null){
|
||||
throw new IllegalArgumentException("Trying to create melee range check node with null check type!");
|
||||
}
|
||||
this.attackerTreeData = attackerTreeData;
|
||||
this.checkType = checkType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(!MeleeTargetingNode.hasTarget(blackboard)){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
Entity target = MeleeTargetingNode.getTarget(blackboard);
|
||||
Vector3d targetPos = EntityUtils.getPosition(target);
|
||||
Vector3d parentPos = EntityUtils.getPosition(entity);
|
||||
switch(this.checkType){
|
||||
case AGGRO: {
|
||||
if(parentPos.distance(targetPos) < this.attackerTreeData.getAggroRange()){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
} break;
|
||||
case ATTACK: {
|
||||
if(parentPos.distance(targetPos) < this.attackerTreeData.getAttackRange()){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
package electrosphere.server.ai.nodes.actions.combat;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
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.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
|
||||
/**
|
||||
* BTree node to seek targets for melee attacks
|
||||
*/
|
||||
public class MeleeTargetingNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The aggro range
|
||||
*/
|
||||
float aggroRange = 0.0f;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param aggroRange The range at which we will pick targets
|
||||
* @param onSuccess The node to execute when a target is found
|
||||
* @param onFailure (Optional) The next node to execute when a target is not found
|
||||
*/
|
||||
public MeleeTargetingNode(float aggroRange){
|
||||
this.aggroRange = aggroRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){
|
||||
if(MeleeTargetingNode.hasTarget(blackboard)){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
//search
|
||||
Entity target = this.searchForTarget(entity, blackboard);
|
||||
if(target == null){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
} else {
|
||||
MeleeTargetingNode.setTarget(blackboard, target);
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a valid target
|
||||
*/
|
||||
private Entity searchForTarget(Entity parent, Blackboard blackboard){
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Realm realm = Globals.realmManager.getEntityRealm(parent);
|
||||
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.LIFE_STATE)){
|
||||
if(current != parent){
|
||||
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
|
||||
if(position.distance(potentialTargetPosition) < aggroRange){
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the blackboard contains a melee target
|
||||
* @param blackboard The blackboard
|
||||
* @return true if there is a target, false otherwise
|
||||
*/
|
||||
public static boolean hasTarget(Blackboard blackboard){
|
||||
return blackboard.has(BlackboardKeys.MELEE_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the target stored in the blackboard
|
||||
* @param blackboard The blackboard
|
||||
* @return The target
|
||||
*/
|
||||
public static Entity getTarget(Blackboard blackboard){
|
||||
return (Entity)blackboard.get(BlackboardKeys.MELEE_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the melee target stored in the blackboard
|
||||
* @param blackboard The blackboard
|
||||
* @param target The target entity
|
||||
*/
|
||||
public static void setTarget(Blackboard blackboard, Entity target){
|
||||
blackboard.put(BlackboardKeys.MELEE_TARGET, target);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package electrosphere.server.ai.nodes.actions.move;
|
||||
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.types.camera.CameraEntityUtils;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.actions.combat.MeleeTargetingNode;
|
||||
import electrosphere.util.math.MathUtils;
|
||||
|
||||
/**
|
||||
* Faces the target
|
||||
*/
|
||||
public class FaceTargetNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public FaceTargetNode(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(MeleeTargetingNode.hasTarget(blackboard)){
|
||||
Entity target = MeleeTargetingNode.getTarget(blackboard);
|
||||
Vector3d parentPos = EntityUtils.getPosition(entity);
|
||||
Vector3d targetPos = EntityUtils.getPosition(target);
|
||||
Quaterniond rotation = MathUtils.calculateRotationFromPointToPoint(parentPos, targetPos);
|
||||
EntityUtils.getRotation(entity).set(rotation);
|
||||
CreatureUtils.setFacingVector(entity, CameraEntityUtils.getFacingVec(rotation));
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package electrosphere.server.ai.nodes.actions.move;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Starts walking
|
||||
*/
|
||||
public class WalkStartNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The facing to move in
|
||||
*/
|
||||
MovementRelativeFacing facing = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public WalkStartNode(MovementRelativeFacing facing){
|
||||
if(facing == null){
|
||||
throw new IllegalArgumentException("Trying to create walk start tree with null facing!");
|
||||
}
|
||||
this.facing = facing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(ServerGroundMovementTree.getServerGroundMovementTree(entity) != null){
|
||||
ServerGroundMovementTree serverGroundMovementTree = ServerGroundMovementTree.getServerGroundMovementTree(entity);
|
||||
if(serverGroundMovementTree.isMoving()){
|
||||
return AITreeNodeResult.RUNNING;
|
||||
} else if(serverGroundMovementTree.canStartMoving()){
|
||||
serverGroundMovementTree.start(this.facing);
|
||||
if(!serverGroundMovementTree.isMoving()){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package electrosphere.server.ai.nodes.actions.move;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Stops walking
|
||||
*/
|
||||
public class WalkStopNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public WalkStopNode(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(ServerGroundMovementTree.getServerGroundMovementTree(entity) != null){
|
||||
ServerGroundMovementTree serverGroundMovementTree = ServerGroundMovementTree.getServerGroundMovementTree(entity);
|
||||
if(serverGroundMovementTree.isMoving()){
|
||||
serverGroundMovementTree.slowdown();
|
||||
return AITreeNodeResult.RUNNING;
|
||||
} else {
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package electrosphere.server.ai.nodes.checks;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.attack.ServerAttackTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Checks if the entity can attack
|
||||
*/
|
||||
public class CanAttackNode implements AITreeNode {
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(!ServerAttackTree.hasAttackTree(entity)){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
ServerAttackTree attackTree = ServerAttackTree.getServerAttackTree(entity);
|
||||
if(attackTree.canAttack()){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package electrosphere.server.ai.nodes.checks;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Checks if the ai is moving
|
||||
*/
|
||||
public class IsMovingNode implements AITreeNode {
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(ServerGroundMovementTree.getServerGroundMovementTree(entity) != null){
|
||||
ServerGroundMovementTree serverGroundMovementTree = ServerGroundMovementTree.getServerGroundMovementTree(entity);
|
||||
if(serverGroundMovementTree.isMoving()){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
} else {
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package electrosphere.server.ai.nodes.checks.equip;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.equip.ServerEquipState;
|
||||
import electrosphere.entity.types.item.ItemUtils;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Checks if the entity has a weapon
|
||||
*/
|
||||
public class HasWeaponNode implements AITreeNode {
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(!ServerEquipState.hasEquipState(entity)){
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
ServerEquipState serverEquipState = ServerEquipState.getEquipState(entity);
|
||||
for(String equippedPointId : serverEquipState.equippedPoints()){
|
||||
Entity item = serverEquipState.getEquippedItemAtPoint(equippedPointId);
|
||||
if(ItemUtils.isWeapon(item)){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package electrosphere.server.ai.nodes.combat;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.state.block.ServerBlockTree;
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.Blackboard;
|
||||
|
||||
/**
|
||||
* Attempts to block
|
||||
*/
|
||||
public class BlockNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The next node to execute
|
||||
*/
|
||||
AITreeNode next = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param next (Optional) The next node to execute
|
||||
*/
|
||||
public BlockNode(AITreeNode next){
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(ServerBlockTree.getServerBlockTree(entity) != null){
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(entity);
|
||||
if(serverBlockTree.isIdle()){
|
||||
serverBlockTree.start();
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package electrosphere.server.ai.nodes.combat;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.Blackboard;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
|
||||
/**
|
||||
* BTree node to seek targets for melee attacks
|
||||
*/
|
||||
public class MeleeTargetingNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The next node to execute
|
||||
*/
|
||||
AITreeNode next = null;
|
||||
|
||||
/**
|
||||
* The attacker tree data
|
||||
*/
|
||||
AttackerTreeData treeData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param next (Optional) The next node to execute
|
||||
*/
|
||||
public MeleeTargetingNode(AITreeNode next){
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode evaluate(Entity entity, Blackboard blackboard) {
|
||||
throw new UnsupportedOperationException("Unimplemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a valid target
|
||||
*/
|
||||
private void searchForTarget(Entity parent){
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Realm realm = Globals.realmManager.getEntityRealm(parent);
|
||||
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.LIFE_STATE)){
|
||||
if(current != parent){
|
||||
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
|
||||
if(position.distance(potentialTargetPosition) < treeData.getAggroRange()){
|
||||
// target = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package electrosphere.server.ai.nodes.meta;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.logger.Logger;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.logger.Logger.LogLevel;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Logs a message
|
||||
*/
|
||||
public class PrintNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The level to print at
|
||||
*/
|
||||
LogLevel level;
|
||||
|
||||
/**
|
||||
* The logger to print into
|
||||
*/
|
||||
Logger logger;
|
||||
|
||||
/**
|
||||
* The message to print
|
||||
*/
|
||||
String message;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param level The logging level to print at
|
||||
* @param logger The logger to print into
|
||||
* @param message The message to print
|
||||
*/
|
||||
public PrintNode(LogLevel level, Logger logger, String message){
|
||||
this.level = level;
|
||||
this.logger = logger;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param level The logging level to print at
|
||||
* @param message The message to print
|
||||
*/
|
||||
public PrintNode(LogLevel level, String message){
|
||||
this.logger = LoggerInterface.loggerAI;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
logger.PRINT(level, message);
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package electrosphere.server.ai.nodes.meta;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.services.TimerService;
|
||||
|
||||
/**
|
||||
* Waits for a provided number of frames
|
||||
*/
|
||||
public class StartTimerNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The id of the timer in the service that this node references
|
||||
*/
|
||||
int timerId = -1;
|
||||
|
||||
/**
|
||||
* Gets the number of frames to wait
|
||||
*/
|
||||
int frameCount = -1;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param next (Optional) The next node to execute
|
||||
*/
|
||||
public StartTimerNode(int frameCount){
|
||||
if(frameCount < 1){
|
||||
LoggerInterface.loggerAI.ERROR(new IllegalArgumentException("Frame count provided to timer is <=0!"));
|
||||
}
|
||||
this.frameCount = frameCount;
|
||||
//create the timer
|
||||
Globals.aiManager.getTimerService().createTimer(this.frameCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
TimerService timerService = Globals.aiManager.getTimerService();
|
||||
if(timerService.isActive(this.timerId)){
|
||||
return AITreeNodeResult.RUNNING;
|
||||
} else {
|
||||
timerService.resetTimer(timerId, frameCount);
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package electrosphere.server.ai.nodes.meta.collections;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A node that has a collection of children
|
||||
*/
|
||||
public interface CollectionNode extends AITreeNode {
|
||||
|
||||
/**
|
||||
* Gets the list of all children that this node can branch into
|
||||
* @return The list of all children this node can branch into
|
||||
*/
|
||||
public List<AITreeNode> getAllChildren();
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package electrosphere.server.ai.nodes.meta.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Executes a random child until it either succeeds or fails
|
||||
*/
|
||||
public class RandomizerNode implements CollectionNode {
|
||||
|
||||
/**
|
||||
* The child nodes of the selector
|
||||
*/
|
||||
List<AITreeNode> children;
|
||||
|
||||
|
||||
/**
|
||||
* The currently executed child
|
||||
*/
|
||||
AITreeNode currentChild = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the randomizer
|
||||
*/
|
||||
public RandomizerNode(List<AITreeNode> children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create randomizer node with no children!");
|
||||
}
|
||||
if(children.size() < 1){
|
||||
throw new IllegalArgumentException("Trying to create randomizer node with no children!");
|
||||
}
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the randomizer
|
||||
*/
|
||||
public RandomizerNode(AITreeNode ... children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create randomizer node with no children!");
|
||||
}
|
||||
if(children.length < 1){
|
||||
throw new IllegalArgumentException("Trying to create randomizer node with no children!");
|
||||
}
|
||||
this.children = Arrays.asList(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(this.currentChild == null){
|
||||
Random random = Globals.aiManager.getRandom();
|
||||
int selectedChild = random.nextInt(children.size());
|
||||
this.currentChild = this.children.get(selectedChild);
|
||||
}
|
||||
AITreeNodeResult childResult = this.currentChild.evaluate(entity, blackboard);
|
||||
if(childResult == AITreeNodeResult.RUNNING){
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
this.currentChild = null;
|
||||
return childResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AITreeNode> getAllChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package electrosphere.server.ai.nodes.meta.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A selector node
|
||||
*/
|
||||
public class SelectorNode implements CollectionNode {
|
||||
|
||||
/**
|
||||
* The child nodes of the selector
|
||||
*/
|
||||
List<AITreeNode> children;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the selector
|
||||
*/
|
||||
public SelectorNode(List<AITreeNode> children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create selector node with no children!");
|
||||
}
|
||||
if(children.size() < 1){
|
||||
throw new IllegalArgumentException("Trying to create selector node with no children!");
|
||||
}
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the selector
|
||||
*/
|
||||
public SelectorNode(AITreeNode ... children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create selector node with no children!");
|
||||
}
|
||||
if(children.length < 1){
|
||||
throw new IllegalArgumentException("Trying to create selector node with no children!");
|
||||
}
|
||||
this.children = Arrays.asList(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
for(AITreeNode child : children){
|
||||
AITreeNodeResult result = child.evaluate(entity, blackboard);
|
||||
if(result != AITreeNodeResult.FAILURE){
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AITreeNode> getAllChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package electrosphere.server.ai.nodes.meta.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A sequence node
|
||||
*/
|
||||
public class SequenceNode implements CollectionNode {
|
||||
|
||||
/**
|
||||
* The child nodes of the sequence
|
||||
*/
|
||||
List<AITreeNode> children;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the sequence
|
||||
*/
|
||||
public SequenceNode(List<AITreeNode> children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create sequence node with no children!");
|
||||
}
|
||||
if(children.size() < 1){
|
||||
throw new IllegalArgumentException("Trying to create sequence node with no children!");
|
||||
}
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param children All the children of the sequence
|
||||
*/
|
||||
public SequenceNode(AITreeNode ... children){
|
||||
if(children == null){
|
||||
throw new IllegalArgumentException("Trying to create sequence node with no children!");
|
||||
}
|
||||
if(children.length < 1){
|
||||
throw new IllegalArgumentException("Trying to create sequence node with no children!");
|
||||
}
|
||||
this.children = Arrays.asList(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
for(AITreeNode child : children){
|
||||
AITreeNodeResult result = child.evaluate(entity, blackboard);
|
||||
if(result != AITreeNodeResult.SUCCESS){
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AITreeNode> getAllChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package electrosphere.server.ai.nodes.meta.debug;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Triggers a debugger
|
||||
*/
|
||||
public class DebuggerNode implements AITreeNode {
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
LoggerInterface.loggerAI.DEBUG("Start debugger!");
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package electrosphere.server.ai.nodes.meta.debug;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.AI;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Publishes a status to the parent ai
|
||||
*/
|
||||
public class PublishStatusNode implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The status
|
||||
*/
|
||||
String status = "Idle";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param status The message to set the status to
|
||||
*/
|
||||
public PublishStatusNode(String status){
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
AI.getAI(entity).setStatus(status);
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A decorator node (ie it has a single direct child)
|
||||
*/
|
||||
public interface DecoratorNode extends AITreeNode {
|
||||
|
||||
/**
|
||||
* Returns the child node of this decorator
|
||||
* @return The child node
|
||||
*/
|
||||
public AITreeNode getChild();
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Forces the return value from a child to be failure
|
||||
*/
|
||||
public class FailerNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child (Optional) Child node to execute before returning
|
||||
*/
|
||||
public FailerNode(AITreeNode child){
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(child != null){
|
||||
child.evaluate(entity, blackboard);
|
||||
}
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Inverts a node's result
|
||||
*/
|
||||
public class InverterNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public InverterNode(AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create inverter node with no children!");
|
||||
}
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
AITreeNodeResult childResult = child.evaluate(entity, blackboard);
|
||||
switch(childResult){
|
||||
case SUCCESS:
|
||||
return AITreeNodeResult.FAILURE;
|
||||
case FAILURE:
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
case RUNNING:
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Repeats execution of a node until a condition is satisfied
|
||||
*/
|
||||
public class RepeaterNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The conditional to check for repetition
|
||||
*/
|
||||
Supplier<Boolean> conditional;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public RepeaterNode(Supplier<Boolean> conditional, AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create repeater node with no children!");
|
||||
}
|
||||
if(conditional == null){
|
||||
throw new IllegalArgumentException("Trying to create repeater node with no conditional for repeat!");
|
||||
}
|
||||
this.conditional = conditional;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
//
|
||||
//evaluate child
|
||||
child.evaluate(entity, blackboard);
|
||||
|
||||
//
|
||||
//determine result
|
||||
boolean conditionCheck = conditional.get();
|
||||
if(conditionCheck){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A node that always returns running
|
||||
*/
|
||||
public class RunnerNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child (Optional) Child node to execute before returning
|
||||
*/
|
||||
public RunnerNode(AITreeNode child){
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(child != null){
|
||||
child.evaluate(entity, blackboard);
|
||||
}
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Forces the return value to be success
|
||||
*/
|
||||
public class SucceederNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child (Optional) Child node to execute before returning
|
||||
*/
|
||||
public SucceederNode(AITreeNode child){
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(child != null){
|
||||
child.evaluate(entity, blackboard);
|
||||
}
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* A node that waits until a timer fires before it executes the child
|
||||
*/
|
||||
public class TimerNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The id for the timer for this node
|
||||
*/
|
||||
int timerId = -1;
|
||||
|
||||
/**
|
||||
* The number of frames to wait for
|
||||
*/
|
||||
int frameCount = -1;
|
||||
|
||||
/**
|
||||
* Controls whether the timer has been started or not
|
||||
*/
|
||||
boolean hasReset = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public TimerNode(AITreeNode child, int frameCount){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create timer node with no children!");
|
||||
}
|
||||
if(frameCount <= 0){
|
||||
throw new IllegalArgumentException("Trying to create timer node with frameCount of zero or lower!");
|
||||
}
|
||||
this.child = child;
|
||||
this.frameCount = frameCount;
|
||||
this.timerId = Globals.aiManager.getTimerService().createTimer();
|
||||
System.out.println("Created timer node with id " + this.timerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
if(Globals.aiManager.getTimerService().isActive(timerId)){
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
if(hasReset){
|
||||
this.hasReset = false;
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
} else {
|
||||
Globals.aiManager.getTimerService().resetTimer(timerId, frameCount);
|
||||
this.hasReset = true;
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
|
||||
/**
|
||||
* Executes until the child returns a specific result
|
||||
*/
|
||||
public class UntilNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The result to check for repetition
|
||||
*/
|
||||
AITreeNodeResult requiredResult = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public UntilNode(AITreeNodeResult requiredResult, AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create until node with no children!");
|
||||
}
|
||||
if(requiredResult == null){
|
||||
throw new IllegalArgumentException("Trying to create until node with no required result!");
|
||||
}
|
||||
this.requiredResult = requiredResult;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
AITreeNodeResult result = child.evaluate(entity, blackboard);
|
||||
if(result == requiredResult){
|
||||
return AITreeNodeResult.SUCCESS;
|
||||
}
|
||||
return AITreeNodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators.conditional;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.DecoratorNode;
|
||||
|
||||
/**
|
||||
* Executes a child node only if the provided conditional succeeds
|
||||
*/
|
||||
public class ConditionalNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The conditional to check for repetition
|
||||
*/
|
||||
Supplier<Boolean> conditional;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public ConditionalNode(Supplier<Boolean> conditional, AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create conditional node with no children!");
|
||||
}
|
||||
if(conditional == null){
|
||||
throw new IllegalArgumentException("Trying to create conditional node with no conditional!");
|
||||
}
|
||||
this.conditional = conditional;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard) {
|
||||
|
||||
//
|
||||
//check if should run child
|
||||
boolean conditionCheck = conditional.get();
|
||||
if(conditionCheck){
|
||||
//
|
||||
//evaluate child
|
||||
return child.evaluate(entity, blackboard);
|
||||
}
|
||||
|
||||
//
|
||||
//Failed to run child
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators.conditional;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.DecoratorNode;
|
||||
|
||||
/**
|
||||
* Runs the child if the conditional fails, otherwise returns failure
|
||||
*/
|
||||
public class OnFailureNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The conditional to check for repetition
|
||||
*/
|
||||
AITreeNode conditional;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public OnFailureNode(AITreeNode conditional, AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create on failure node with no children!");
|
||||
}
|
||||
if(conditional == null){
|
||||
throw new IllegalArgumentException("Trying to create on failure node with no conditional!");
|
||||
}
|
||||
this.conditional = conditional;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){
|
||||
|
||||
//
|
||||
//check if should run child
|
||||
AITreeNodeResult conditionalResult = conditional.evaluate(entity, blackboard);
|
||||
if(conditionalResult == AITreeNodeResult.FAILURE){
|
||||
//
|
||||
//evaluate child
|
||||
return child.evaluate(entity, blackboard);
|
||||
}
|
||||
|
||||
//
|
||||
//Failed to run child
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package electrosphere.server.ai.nodes.meta.decorators.conditional;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.server.ai.blackboard.Blackboard;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.DecoratorNode;
|
||||
|
||||
/**
|
||||
* Runs the child if the conditional succeeds, otherwise returns failure
|
||||
*/
|
||||
public class OnSuccessNode implements DecoratorNode {
|
||||
|
||||
/**
|
||||
* The child node
|
||||
*/
|
||||
AITreeNode child;
|
||||
|
||||
/**
|
||||
* The conditional to check for repetition
|
||||
*/
|
||||
AITreeNode conditional;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param child The child node
|
||||
*/
|
||||
public OnSuccessNode(AITreeNode conditional, AITreeNode child){
|
||||
if(child == null){
|
||||
throw new IllegalArgumentException("Trying to create on success node with no children!");
|
||||
}
|
||||
if(conditional == null){
|
||||
throw new IllegalArgumentException("Trying to create on success node with no conditional!");
|
||||
}
|
||||
this.conditional = conditional;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){
|
||||
|
||||
//
|
||||
//check if should run child
|
||||
AITreeNodeResult conditionalResult = conditional.evaluate(entity, blackboard);
|
||||
if(conditionalResult == AITreeNodeResult.SUCCESS){
|
||||
//
|
||||
//evaluate child
|
||||
return child.evaluate(entity, blackboard);
|
||||
}
|
||||
|
||||
//
|
||||
//Failed to run child
|
||||
return AITreeNodeResult.FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
package electrosphere.server.ai.nodes.movement;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
|
||||
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.Blackboard;
|
||||
|
||||
/**
|
||||
* Moves the ai into optimial positioning for melee combat
|
||||
*/
|
||||
public class MeleeMovement implements AITreeNode {
|
||||
|
||||
/**
|
||||
* The next node to execute
|
||||
*/
|
||||
AITreeNode next = null;
|
||||
|
||||
/**
|
||||
* The attacker tree data
|
||||
*/
|
||||
AttackerTreeData treeData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param next (Optional) The next node to execute
|
||||
*/
|
||||
public MeleeMovement(AITreeNode next){
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AITreeNode evaluate(Entity entity, Blackboard blackboard) {
|
||||
throw new UnsupportedOperationException("Unimplemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the target is within the aggression range
|
||||
*/
|
||||
private boolean inAggroRange(Entity parent, Entity target){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < treeData.getAggroRange()){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the target is within range of an attack animation
|
||||
* @return
|
||||
*/
|
||||
private boolean inAttackRange(Entity parent, Entity target){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < treeData.getAttackRange()){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to move towards the target
|
||||
*/
|
||||
private void moveToTarget(Entity parent, Entity target){
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
Vector3d characterPosition = EntityUtils.getPosition(parent);
|
||||
Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).normalize();
|
||||
CreatureUtils.setFacingVector(parent, new Vector3d((float)moveVector.x,(float)moveVector.y,(float)moveVector.z));
|
||||
ServerGroundMovementTree characterMoveTree = (ServerGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(parent);
|
||||
if(characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.IDLE || characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.SLOWDOWN){
|
||||
characterMoveTree.start(MovementRelativeFacing.FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package electrosphere.server.ai.services;
|
||||
|
||||
/**
|
||||
* A service
|
||||
*/
|
||||
public interface AIService {
|
||||
|
||||
/**
|
||||
* Executes the service
|
||||
*/
|
||||
public void exec();
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package electrosphere.server.ai.services;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
|
||||
public class TimerService implements AIService {
|
||||
|
||||
/**
|
||||
* The map of timer id -> number of frames left on the timer
|
||||
*/
|
||||
Map<Integer,Integer> timerMap = new HashMap<Integer,Integer>();
|
||||
|
||||
/**
|
||||
* The iterator for timer ids
|
||||
*/
|
||||
int idIterator = 0;
|
||||
|
||||
/**
|
||||
* Lock for ids so we don't overlap
|
||||
*/
|
||||
Semaphore idLock = new Semaphore(1);
|
||||
|
||||
@Override
|
||||
public void exec() {
|
||||
for(Integer timerId : timerMap.keySet()){
|
||||
int currentTime = timerMap.get(timerId);
|
||||
int newTime = currentTime - 1;
|
||||
if(newTime < 0){
|
||||
newTime = 0;
|
||||
}
|
||||
timerMap.put(timerId,newTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a timer
|
||||
* @return The id of the timer
|
||||
*/
|
||||
public int createTimer(){
|
||||
int id = 0;
|
||||
idLock.acquireUninterruptibly();
|
||||
id = idIterator++;
|
||||
timerMap.put(id,0);
|
||||
idLock.release();
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a timer
|
||||
* @param frameCount The number of frames left in the timer
|
||||
* @return The id of the timer
|
||||
*/
|
||||
public int createTimer(int frameCount){
|
||||
int id = 0;
|
||||
idLock.acquireUninterruptibly();
|
||||
id = idIterator++;
|
||||
timerMap.put(id,frameCount);
|
||||
idLock.release();
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a timer
|
||||
* @param timerId The id of the timer
|
||||
*/
|
||||
public void deleteTimer(int timerId){
|
||||
timerMap.remove(timerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the timer is still active
|
||||
* @param timerId The id of the timer
|
||||
* @return true if still active, false if has completed or has been deleted
|
||||
*/
|
||||
public boolean isActive(int timerId){
|
||||
if(!timerMap.containsKey(timerId)){
|
||||
return false;
|
||||
}
|
||||
return timerMap.get(timerId) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a timer to a given frame count
|
||||
* @param timerId The id of the timer
|
||||
* @param frameCount The number of frames remaining for the timer
|
||||
*/
|
||||
public void resetTimer(int timerId, int frameCount){
|
||||
if(!timerMap.containsKey(timerId)){
|
||||
LoggerInterface.loggerAI.ERROR(new IllegalArgumentException("Trying to reset a timer that does not exist!"));
|
||||
}
|
||||
timerMap.put(timerId,frameCount);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
package electrosphere.server.ai.trees.creature;
|
||||
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.combat.BlockNode;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.meta.collections.SequenceNode;
|
||||
import electrosphere.server.ai.trees.creature.melee.MeleeAITree;
|
||||
|
||||
/**
|
||||
* Figures out how if it should attack and how it can, then executes that subtree
|
||||
@ -17,8 +19,10 @@ public class AttackerAITree {
|
||||
* Creates an attacker ai tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(){
|
||||
return new BlockNode(null);
|
||||
public static AITreeNode create(AttackerTreeData attackerTreeData){
|
||||
return new SequenceNode(
|
||||
MeleeAITree.create(attackerTreeData)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
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.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.AITreeNode.AITreeNodeResult;
|
||||
import electrosphere.server.ai.nodes.actions.combat.AttackStartNode;
|
||||
import electrosphere.server.ai.nodes.actions.combat.MeleeRangeCheckNode;
|
||||
import electrosphere.server.ai.nodes.actions.combat.MeleeTargetingNode;
|
||||
import electrosphere.server.ai.nodes.actions.combat.MeleeRangeCheckNode.MeleeRangeCheckType;
|
||||
import electrosphere.server.ai.nodes.actions.move.FaceTargetNode;
|
||||
import electrosphere.server.ai.nodes.actions.move.WalkStartNode;
|
||||
import electrosphere.server.ai.nodes.actions.move.WalkStopNode;
|
||||
import electrosphere.server.ai.nodes.checks.IsMovingNode;
|
||||
import electrosphere.server.ai.nodes.checks.equip.HasWeaponNode;
|
||||
import electrosphere.server.ai.nodes.meta.collections.RandomizerNode;
|
||||
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.FailerNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.InverterNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.SucceederNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.TimerNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.UntilNode;
|
||||
import electrosphere.server.ai.nodes.meta.decorators.conditional.OnFailureNode;
|
||||
|
||||
/**
|
||||
* Creates a melee ai tree
|
||||
*/
|
||||
public class MeleeAITree {
|
||||
|
||||
/**
|
||||
* Name of the tree
|
||||
*/
|
||||
public static final String TREE_NAME = "Melee";
|
||||
|
||||
/**
|
||||
* Creates a melee tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(AttackerTreeData attackerTreeData){
|
||||
|
||||
return new SequenceNode(
|
||||
//preconditions here
|
||||
new HasWeaponNode(),
|
||||
new MeleeTargetingNode(attackerTreeData.getAggroRange()),
|
||||
//determine strategy
|
||||
new RandomizerNode(
|
||||
//wait
|
||||
new SequenceNode(
|
||||
new PublishStatusNode("Waiting"),
|
||||
new FaceTargetNode(),
|
||||
new TimerNode(new SucceederNode(null), 1200)
|
||||
),
|
||||
|
||||
//move to hover distance
|
||||
new SequenceNode(
|
||||
new PublishStatusNode("Strafing right"),
|
||||
new InverterNode(new OnFailureNode(
|
||||
new WalkStartNode(MovementRelativeFacing.RIGHT),
|
||||
new FailerNode(null)
|
||||
)),
|
||||
new FaceTargetNode(),
|
||||
new TimerNode(new SucceederNode(null), 600),
|
||||
new SucceederNode(new WalkStopNode())
|
||||
),
|
||||
|
||||
//move from hover distance to melee range
|
||||
new SequenceNode(
|
||||
new PublishStatusNode("Strafing left"),
|
||||
new InverterNode(new OnFailureNode(
|
||||
new WalkStartNode(MovementRelativeFacing.LEFT),
|
||||
new FailerNode(null)
|
||||
)),
|
||||
new FaceTargetNode(),
|
||||
new TimerNode(new SucceederNode(null), 600),
|
||||
new SucceederNode(new WalkStopNode())
|
||||
),
|
||||
|
||||
//move towards target and attack
|
||||
new SequenceNode(
|
||||
new PublishStatusNode("Attack target"),
|
||||
//move towards target if its outside of melee range
|
||||
new UntilNode(AITreeNodeResult.SUCCESS,
|
||||
//or
|
||||
new SelectorNode(
|
||||
//in range
|
||||
new MeleeRangeCheckNode(attackerTreeData,MeleeRangeCheckType.ATTACK),
|
||||
//approaching target
|
||||
new SequenceNode(
|
||||
new PublishStatusNode("Approaching target"),
|
||||
new FaceTargetNode(),
|
||||
new OnFailureNode(new IsMovingNode(), new WalkStartNode(MovementRelativeFacing.FORWARD)),
|
||||
new MeleeRangeCheckNode(attackerTreeData,MeleeRangeCheckType.ATTACK)
|
||||
)
|
||||
)
|
||||
),
|
||||
//stop walking now that we're in range
|
||||
new PublishStatusNode("Slowing down"),
|
||||
new WalkStopNode(),
|
||||
new UntilNode(AITreeNodeResult.FAILURE, new IsMovingNode()),
|
||||
new PublishStatusNode("Attacking"),
|
||||
new AttackStartNode()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
package electrosphere.server.ai.trees.test;
|
||||
|
||||
import electrosphere.game.data.creature.type.ai.BlockerTreeData;
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.combat.BlockNode;
|
||||
import electrosphere.server.ai.nodes.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.actions.BlockStartNode;
|
||||
import electrosphere.server.ai.nodes.actions.combat.MeleeTargetingNode;
|
||||
import electrosphere.server.ai.nodes.actions.move.FaceTargetNode;
|
||||
import electrosphere.server.ai.nodes.meta.collections.SequenceNode;
|
||||
|
||||
/**
|
||||
* Creates a blocker test tree
|
||||
@ -19,7 +22,11 @@ public class BlockerAITree {
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(BlockerTreeData data){
|
||||
return new BlockNode(null);
|
||||
return new SequenceNode(
|
||||
new BlockStartNode(),
|
||||
new MeleeTargetingNode(5.0f),
|
||||
new FaceTargetNode()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user