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" : [
|
"aiTrees" : [
|
||||||
{
|
{
|
||||||
"name" : "Blocker"
|
"name" : "Attacker",
|
||||||
|
"aggroRange" : 10,
|
||||||
|
"attackRange" : 0.8
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"boneGroups" : [
|
"boneGroups" : [
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
+ when you grab the sword, a tutorial popup appears to tell you how to use in
|
+ 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
|
+ 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
|
+ when popup is accepted, spawn an enemy with an effect
|
||||||
enemy ai
|
|
||||||
review combat code (lifestate, damage calculation, etc)
|
review combat code (lifestate, damage calculation, etc)
|
||||||
Maybe a fade-out before deleting entity on death?
|
Maybe a fade-out before deleting entity on death?
|
||||||
|
|
||||||
@ -15,6 +14,9 @@
|
|||||||
+ fix the vibes
|
+ fix the vibes
|
||||||
Stability
|
Stability
|
||||||
Movement penalty while swinging weapon
|
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
|
+ bug fixes
|
||||||
Fix grass rendering distance
|
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
|
Windup and Cooldown animations on 2h block
|
||||||
AI rearchitecture to actually do behavior trees now that I know what they are
|
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
|
# TODO
|
||||||
|
|
||||||
|
|||||||
@ -431,7 +431,7 @@ public class Globals {
|
|||||||
//script engine
|
//script engine
|
||||||
scriptEngine = new ScriptEngine();
|
scriptEngine = new ScriptEngine();
|
||||||
//ai manager
|
//ai manager
|
||||||
aiManager = new AIManager();
|
aiManager = new AIManager(0);
|
||||||
//realm & data cell manager
|
//realm & data cell manager
|
||||||
realmManager = new RealmManager();
|
realmManager = new RealmManager();
|
||||||
entityDataCellMapper = new EntityDataCellMapper();
|
entityDataCellMapper = new EntityDataCellMapper();
|
||||||
|
|||||||
@ -511,6 +511,18 @@ public class ServerAttackTree implements BehaviorTree {
|
|||||||
return rVal;
|
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
|
* Gets the next attack move
|
||||||
* @param moveset The moveset
|
* @param moveset The moveset
|
||||||
@ -570,6 +582,15 @@ public class ServerAttackTree implements BehaviorTree {
|
|||||||
this.collidedEntities.add(target);
|
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> Automatically generated </p>
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@ -431,6 +431,18 @@ public class ServerGroundMovementTree implements BehaviorTree {
|
|||||||
networkMessageQueue.add(networkMessage);
|
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(){
|
public boolean canStartMoving(){
|
||||||
boolean rVal = true;
|
boolean rVal = true;
|
||||||
return rVal;
|
return rVal;
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import electrosphere.entity.Entity;
|
|||||||
import electrosphere.entity.EntityDataStrings;
|
import electrosphere.entity.EntityDataStrings;
|
||||||
import electrosphere.entity.EntityUtils;
|
import electrosphere.entity.EntityUtils;
|
||||||
import electrosphere.entity.btree.BehaviorTree;
|
import electrosphere.entity.btree.BehaviorTree;
|
||||||
|
import electrosphere.entity.types.camera.CameraEntityUtils;
|
||||||
|
import electrosphere.entity.types.creature.CreatureUtils;
|
||||||
import electrosphere.net.parser.net.message.EntityMessage;
|
import electrosphere.net.parser.net.message.EntityMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,6 +42,8 @@ public class ClientPhysicsSyncTree implements BehaviorTree {
|
|||||||
public void simulate(float deltaTime) {
|
public void simulate(float deltaTime) {
|
||||||
if(!hasPushesMessage && latestMessage != null){
|
if(!hasPushesMessage && latestMessage != null){
|
||||||
this.hasPushesMessage = true;
|
this.hasPushesMessage = true;
|
||||||
|
//
|
||||||
|
//get values
|
||||||
Vector3d position = new Vector3d(latestMessage.getpositionX(),latestMessage.getpositionY(),latestMessage.getpositionZ());
|
Vector3d position = new Vector3d(latestMessage.getpositionX(),latestMessage.getpositionY(),latestMessage.getpositionZ());
|
||||||
Quaterniond rotationFromServer = new Quaterniond(latestMessage.getrotationX(),latestMessage.getrotationY(),latestMessage.getrotationZ(),latestMessage.getrotationW());
|
Quaterniond rotationFromServer = new Quaterniond(latestMessage.getrotationX(),latestMessage.getrotationY(),latestMessage.getrotationZ(),latestMessage.getrotationW());
|
||||||
Vector3d linearVelocity = new Vector3d(latestMessage.getlinVelX(), latestMessage.getlinVelY(), latestMessage.getlinVelZ());
|
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 linearForce = new Vector3d(latestMessage.getlinForceX(), latestMessage.getlinForceY(), latestMessage.getlinForceZ());
|
||||||
Vector3d angularForce = new Vector3d();
|
Vector3d angularForce = new Vector3d();
|
||||||
DBody body = PhysicsEntityUtils.getDBody(parent);
|
DBody body = PhysicsEntityUtils.getDBody(parent);
|
||||||
|
|
||||||
|
//
|
||||||
|
//Synchronize data
|
||||||
EntityUtils.getPosition(parent).set(position);
|
EntityUtils.getPosition(parent).set(position);
|
||||||
EntityUtils.getRotation(parent).set(rotationFromServer);
|
EntityUtils.getRotation(parent).set(rotationFromServer);
|
||||||
PhysicsUtils.synchronizeData(Globals.clientSceneWrapper.getCollisionEngine(), body, position, rotationFromServer, linearVelocity, angularVelocity, linearForce, angularForce);
|
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);
|
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
|
* Gets the rotation matrix from a yaw and pitch
|
||||||
* @param yaw The yaw
|
* @param yaw The yaw
|
||||||
|
|||||||
@ -15,11 +15,6 @@ public class AttackerTreeData implements AITreeData {
|
|||||||
*/
|
*/
|
||||||
float attackRange;
|
float attackRange;
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of frames to wait before changing between states
|
|
||||||
*/
|
|
||||||
int stateChangeTimeout;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the range at which this entity will locate enemies
|
* Gets the range at which this entity will locate enemies
|
||||||
* @return The range
|
* @return The range
|
||||||
@ -36,14 +31,6 @@ public class AttackerTreeData implements AITreeData {
|
|||||||
return attackRange;
|
return attackRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of frames to wait before changing between states
|
|
||||||
* @return The number of frames
|
|
||||||
*/
|
|
||||||
public int getStateChangeTimeout(){
|
|
||||||
return stateChangeTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Attacker";
|
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 loggerAudio;
|
||||||
public static Logger loggerUI;
|
public static Logger loggerUI;
|
||||||
public static Logger loggerScripts;
|
public static Logger loggerScripts;
|
||||||
|
public static Logger loggerAI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes all logic objects
|
* Initializes all logic objects
|
||||||
@ -36,6 +37,7 @@ public class LoggerInterface {
|
|||||||
loggerAudio = new Logger(LogLevel.WARNING);
|
loggerAudio = new Logger(LogLevel.WARNING);
|
||||||
loggerUI = new Logger(LogLevel.WARNING);
|
loggerUI = new Logger(LogLevel.WARNING);
|
||||||
loggerScripts = new Logger(LogLevel.WARNING);
|
loggerScripts = new Logger(LogLevel.WARNING);
|
||||||
|
loggerAI = new Logger(LogLevel.LOOP_DEBUG);
|
||||||
loggerStartup.INFO("Initialized loggers");
|
loggerStartup.INFO("Initialized loggers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package electrosphere.menu.debug;
|
|||||||
import electrosphere.engine.Globals;
|
import electrosphere.engine.Globals;
|
||||||
import electrosphere.renderer.ui.imgui.ImGuiWindow;
|
import electrosphere.renderer.ui.imgui.ImGuiWindow;
|
||||||
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
|
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
|
||||||
|
import electrosphere.server.ai.AI;
|
||||||
import imgui.ImGui;
|
import imgui.ImGui;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +37,12 @@ public class ImGuiAI {
|
|||||||
if(ImGui.button("Toggle AI Manager")){
|
if(ImGui.button("Toggle AI Manager")){
|
||||||
Globals.aiManager.setActive(!Globals.aiManager.isActive());
|
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.EntityDataStrings;
|
||||||
import electrosphere.entity.types.creature.CreatureUtils;
|
import electrosphere.entity.types.creature.CreatureUtils;
|
||||||
import electrosphere.game.data.creature.type.ai.AITreeData;
|
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.BlockerTreeData;
|
||||||
import electrosphere.logger.LoggerInterface;
|
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;
|
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,6 +46,11 @@ public class AI {
|
|||||||
*/
|
*/
|
||||||
List<AITreeNode> evaluatedNodes = new LinkedList<AITreeNode>();
|
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
|
* 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
|
* @param treeData The list of data on trees to be provided
|
||||||
@ -56,6 +65,9 @@ public class AI {
|
|||||||
case BlockerAITree.TREE_NAME: {
|
case BlockerAITree.TREE_NAME: {
|
||||||
rVal.rootNode = BlockerAITree.create((BlockerTreeData) aiData);
|
rVal.rootNode = BlockerAITree.create((BlockerTreeData) aiData);
|
||||||
} break;
|
} break;
|
||||||
|
case AttackerAITree.TREE_NAME: {
|
||||||
|
rVal.rootNode = AttackerAITree.create((AttackerTreeData) aiData);
|
||||||
|
} break;
|
||||||
default: {
|
default: {
|
||||||
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName()));
|
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName()));
|
||||||
} break;
|
} break;
|
||||||
@ -77,21 +89,7 @@ public class AI {
|
|||||||
*/
|
*/
|
||||||
protected void simulate(){
|
protected void simulate(){
|
||||||
if(this.shouldExecute()){
|
if(this.shouldExecute()){
|
||||||
AITreeNode currentNode = this.rootNode;
|
this.rootNode.evaluate(this.parent, this.blackboard);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,5 +138,21 @@ public class AI {
|
|||||||
private boolean shouldExecute(){
|
private boolean shouldExecute(){
|
||||||
return !CreatureUtils.hasControllerPlayerId(this.parent);
|
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.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import electrosphere.entity.Entity;
|
import electrosphere.entity.Entity;
|
||||||
import electrosphere.game.data.creature.type.ai.AITreeData;
|
import electrosphere.game.data.creature.type.ai.AITreeData;
|
||||||
import electrosphere.logger.LoggerInterface;
|
import electrosphere.logger.LoggerInterface;
|
||||||
|
import electrosphere.server.ai.services.TimerService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server manager for all entity AIs
|
* Server manager for all entity AIs
|
||||||
@ -33,18 +35,32 @@ public class AIManager {
|
|||||||
* Controls whether the ai manager should simulate each frame or not
|
* Controls whether the ai manager should simulate each frame or not
|
||||||
*/
|
*/
|
||||||
boolean active = true;
|
boolean active = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timer service
|
||||||
|
*/
|
||||||
|
TimerService timerService = new TimerService();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The random of the ai
|
||||||
|
*/
|
||||||
|
Random random = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public AIManager(){
|
public AIManager(long seed){
|
||||||
|
this.random = new Random(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates all AIs currently available
|
* Simulates all AIs currently available
|
||||||
*/
|
*/
|
||||||
public void simulate(){
|
public void simulate(){
|
||||||
|
//exec the services
|
||||||
|
execServices();
|
||||||
|
|
||||||
|
//simulate each tree
|
||||||
if(isActive()){
|
if(isActive()){
|
||||||
for(AI ai : aiList){
|
for(AI ai : aiList){
|
||||||
ai.simulate();
|
ai.simulate();
|
||||||
@ -89,6 +105,7 @@ public class AIManager {
|
|||||||
aiList.add(ai);
|
aiList.add(ai);
|
||||||
entityAIMap.put(entity,ai);
|
entityAIMap.put(entity,ai);
|
||||||
aiEntityMap.put(ai,entity);
|
aiEntityMap.put(ai,entity);
|
||||||
|
AI.setAI(entity, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,5 +118,28 @@ public class AIManager {
|
|||||||
aiEntityMap.remove(targetAI);
|
aiEntityMap.remove(targetAI);
|
||||||
entityAIMap.remove(entity);
|
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.HashMap;
|
||||||
import java.util.Map;
|
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;
|
package electrosphere.server.ai.trees.creature;
|
||||||
|
|
||||||
import electrosphere.server.ai.AITreeNode;
|
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||||
import electrosphere.server.ai.nodes.combat.BlockNode;
|
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
|
* 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
|
* Creates an attacker ai tree
|
||||||
* @return The root node of the tree
|
* @return The root node of the tree
|
||||||
*/
|
*/
|
||||||
public static AITreeNode create(){
|
public static AITreeNode create(AttackerTreeData attackerTreeData){
|
||||||
return new BlockNode(null);
|
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;
|
package electrosphere.server.ai.trees.test;
|
||||||
|
|
||||||
import electrosphere.game.data.creature.type.ai.BlockerTreeData;
|
import electrosphere.game.data.creature.type.ai.BlockerTreeData;
|
||||||
import electrosphere.server.ai.AITreeNode;
|
import electrosphere.server.ai.nodes.AITreeNode;
|
||||||
import electrosphere.server.ai.nodes.combat.BlockNode;
|
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
|
* Creates a blocker test tree
|
||||||
@ -19,7 +22,11 @@ public class BlockerAITree {
|
|||||||
* @return The root node of the tree
|
* @return The root node of the tree
|
||||||
*/
|
*/
|
||||||
public static AITreeNode create(BlockerTreeData data){
|
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