Ai rearchitecture for real btrees
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
d44fdc5673
commit
bdec668fb8
@ -564,6 +564,7 @@ Fix katana is frustum culled incorrectly
|
||||
Fix rendering pipelines being broken when the katana is not drawn
|
||||
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
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
@ -155,6 +155,14 @@ public class ServerBlockTree implements BehaviorTree {
|
||||
return this.state == BlockState.BLOCKING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this tree is in any active state or not (ie is it playing an animation, blocking, etc)
|
||||
* @return true if no animation is happening, false otherwise
|
||||
*/
|
||||
public boolean isIdle(){
|
||||
return this.state == BlockState.NOT_BLOCKING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block system data for this tree
|
||||
* @return the data if it exists, otherwise null
|
||||
|
||||
@ -8,8 +8,8 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.creature.Attacker;
|
||||
import electrosphere.server.ai.creature.Blocker;
|
||||
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||
import electrosphere.server.ai.trees.creature.AttackerAITree;
|
||||
|
||||
/**
|
||||
* Deserializes ai tree data types
|
||||
@ -20,9 +20,9 @@ public class AITreeDataSerializer implements JsonDeserializer<AITreeData> {
|
||||
public AITreeData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
|
||||
switch(json.getAsJsonObject().get("name").getAsString()){
|
||||
case Attacker.TREE_NAME:
|
||||
case AttackerAITree.TREE_NAME:
|
||||
return context.deserialize(json, AttackerTreeData.class);
|
||||
case Blocker.TREE_NAME:
|
||||
case BlockerAITree.TREE_NAME:
|
||||
return context.deserialize(json, BlockerTreeData.class);
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ 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;
|
||||
|
||||
/**
|
||||
@ -37,14 +36,6 @@ public class ImGuiAI {
|
||||
if(ImGui.button("Toggle AI Manager")){
|
||||
Globals.aiManager.setActive(!Globals.aiManager.isActive());
|
||||
}
|
||||
|
||||
if(ImGui.collapsingHeader("AI Statuses")){
|
||||
for(AI ai : Globals.aiManager.getAIList()){
|
||||
if(ai.getCurrentTree() != null){
|
||||
ImGui.text("Entity ID " + ai.getParent().getId() + " - Active Tree: " + ai.getCurrentTree().getCurrentStateName() + " - Priority: " + ai.getCurrentTree().getCurrentStatePriority());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,19 +3,13 @@ package electrosphere.server.ai;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityDataStrings;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
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.creature.Attacker;
|
||||
import electrosphere.server.ai.creature.Blocker;
|
||||
import electrosphere.server.ai.trees.test.BlockerAITree;
|
||||
|
||||
/**
|
||||
* A collection of AITrees that are attached to a single entity.
|
||||
@ -34,14 +28,19 @@ public class AI {
|
||||
Entity parent;
|
||||
|
||||
/**
|
||||
* The list of trees for this ai in particular
|
||||
* The root node of the behavior tree
|
||||
*/
|
||||
List<AITree> trees = new LinkedList<AITree>();
|
||||
AITreeNode rootNode;
|
||||
|
||||
/**
|
||||
* The currently active tree
|
||||
* The blackboard for the tree
|
||||
*/
|
||||
AITree currentTree = null;
|
||||
Blackboard blackboard = new Blackboard();
|
||||
|
||||
/**
|
||||
* Internal usage, used to track the nodes that have been evaluated and ensure we're not looping
|
||||
*/
|
||||
List<AITreeNode> evaluatedNodes = new LinkedList<AITreeNode>();
|
||||
|
||||
/**
|
||||
* Constructs an AI from a list of trees that should be present on the ai
|
||||
@ -54,11 +53,8 @@ public class AI {
|
||||
//attach all trees
|
||||
for(AITreeData aiData : treeData){
|
||||
switch(aiData.getName()){
|
||||
case Attacker.TREE_NAME:
|
||||
rVal.trees.add(Attacker.construct(parent, (AttackerTreeData) aiData));
|
||||
break;
|
||||
case Blocker.TREE_NAME: {
|
||||
rVal.trees.add(Blocker.construct(parent, (BlockerTreeData) aiData));
|
||||
case BlockerAITree.TREE_NAME: {
|
||||
rVal.rootNode = BlockerAITree.create((BlockerTreeData) aiData);
|
||||
} break;
|
||||
default: {
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName()));
|
||||
@ -80,48 +76,23 @@ public class AI {
|
||||
* Simulates a single frame of this ai. Only runs simulation logic for the highest priority tree
|
||||
*/
|
||||
protected void simulate(){
|
||||
//
|
||||
//Check basic values of entity to make sure the ai trees aren't changing state when updating state and priority
|
||||
Vector3d currentPos = new Vector3d(EntityUtils.getPosition(parent));
|
||||
Quaterniond currentRot = new Quaterniond(EntityUtils.getRotation(parent));
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//Update state and find the highest priority tree
|
||||
for(AITree tree : trees){
|
||||
tree.updateStateAndPriority();
|
||||
if(currentTree == null){
|
||||
currentTree = tree;
|
||||
} else if(currentTree.getCurrentStatePriority() < tree.getCurrentStatePriority()){
|
||||
currentTree = tree;
|
||||
//
|
||||
//evaluate node
|
||||
evaluatedNodes.add(currentNode);
|
||||
currentNode = currentNode.evaluate(this.parent, this.blackboard);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//Actual check to see if state has changed (throw error if it does)
|
||||
Vector3d postUpdatePos = EntityUtils.getPosition(parent);
|
||||
Quaterniond postUpdateRot = EntityUtils.getRotation(parent);
|
||||
if(postUpdatePos.distance(currentPos) > 0){
|
||||
String message = "An AI tree changed entity state while it was updating its priority!\n" +
|
||||
"This is an illegal operation!\n" +
|
||||
"All entity updates should be in the simulate method!" +
|
||||
"(The position changed)";
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalStateException(message));
|
||||
}
|
||||
if(postUpdateRot.dot(currentRot) < ERROR_CHECK_ROT_THRESHOLD){
|
||||
String message = "An AI tree changed entity state while it was updating its priority!\n" +
|
||||
"This is an illegal operation!\n" +
|
||||
"All entity updates should be in the simulate method!" +
|
||||
"(The rotation changed) " + postUpdateRot.dot(currentRot);
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalStateException(message));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//Simulate the highest priority tree
|
||||
if(currentTree != null && !CreatureUtils.hasControllerPlayerId(this.parent)){
|
||||
currentTree.simulate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,11 +126,19 @@ public class AI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current highest priority tree
|
||||
* @return The highest priority tree
|
||||
* Gets the root node of the behavior tree
|
||||
* @return The root node
|
||||
*/
|
||||
public AITree getCurrentTree(){
|
||||
return this.currentTree;
|
||||
public AITreeNode getRootNode(){
|
||||
return this.rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ai should simulate or not
|
||||
* @return true if should simulate, false otherwise
|
||||
*/
|
||||
private boolean shouldExecute(){
|
||||
return !CreatureUtils.hasControllerPlayerId(this.parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
package electrosphere.server.ai;
|
||||
|
||||
/**
|
||||
* A behavior tree encapsulating some type of
|
||||
*/
|
||||
public interface AITree {
|
||||
|
||||
/**
|
||||
* Simulates a single frame of logic for the ai.
|
||||
* This is where the ai actually makes the entity do something
|
||||
*/
|
||||
public void simulate();
|
||||
|
||||
/**
|
||||
* Gets the name of the tree
|
||||
* @return The tree's name
|
||||
*/
|
||||
public String getTreeName();
|
||||
|
||||
/**
|
||||
* Updates the state and priority of this tree.
|
||||
* This should calculate the goal of this tree every frame.
|
||||
* All state transitions should be in here.
|
||||
*/
|
||||
public void updateStateAndPriority();
|
||||
|
||||
/**
|
||||
* Gets the name of the current state of this ai
|
||||
* @return The name
|
||||
*/
|
||||
public String getCurrentStateName();
|
||||
|
||||
/**
|
||||
* Gets the priority of the current state of this ai
|
||||
* The priority increases with the value of the int. IE, a priority of 1 is of greater importance than a priority of 0.
|
||||
* @return The priority
|
||||
*/
|
||||
public int getCurrentStatePriority();
|
||||
|
||||
}
|
||||
18
src/main/java/electrosphere/server/ai/AITreeNode.java
Normal file
18
src/main/java/electrosphere/server/ai/AITreeNode.java
Normal file
@ -0,0 +1,18 @@
|
||||
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);
|
||||
|
||||
}
|
||||
57
src/main/java/electrosphere/server/ai/Blackboard.java
Normal file
57
src/main/java/electrosphere/server/ai/Blackboard.java
Normal file
@ -0,0 +1,57 @@
|
||||
package electrosphere.server.ai;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Blackboard for storing data with the tree
|
||||
*/
|
||||
public class Blackboard {
|
||||
|
||||
/**
|
||||
* The values in the blackboard
|
||||
*/
|
||||
Map<String,Object> values = new HashMap<String,Object>();
|
||||
|
||||
/**
|
||||
* Puts a value into the blackboard
|
||||
* @param key The key
|
||||
* @param value The value
|
||||
*/
|
||||
public void put(String key, Object value){
|
||||
values.put(key,value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a value from the blackboard
|
||||
* @param key The key of the value
|
||||
* @return The value if it exists, null otherwise
|
||||
*/
|
||||
public Object get(String key){
|
||||
if(this.has(key)){
|
||||
return values.get(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the blackboard contains a value at a given key
|
||||
* @param key The key
|
||||
* @return true if the blackboard has a value for that key, false otherwise
|
||||
*/
|
||||
public boolean has(String key){
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a value from the blackboard
|
||||
* @param key The key to delete the value from
|
||||
*/
|
||||
public void delete(String key){
|
||||
if(this.has(key)){
|
||||
values.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,344 +0,0 @@
|
||||
package electrosphere.server.ai.creature;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.attack.ServerAttackTree;
|
||||
import electrosphere.entity.state.block.ServerBlockTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.game.data.creature.type.ai.AttackerTreeData;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.server.ai.AITree;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
|
||||
/**
|
||||
* Keeps track of targets and instructs the ai to attack
|
||||
*/
|
||||
public class Attacker implements AITree {
|
||||
|
||||
/**
|
||||
* The name of the tree type
|
||||
*/
|
||||
public static final String TREE_NAME = "Attacker";
|
||||
|
||||
/**
|
||||
* Different states available to the Attacker tree
|
||||
*/
|
||||
public enum AttackerState {
|
||||
|
||||
/**
|
||||
* Not doing anything
|
||||
*/
|
||||
IDLE,
|
||||
|
||||
/**
|
||||
* Trying to close the distance between itself and its target
|
||||
*/
|
||||
MOVING_INTO_ATTACKING_RANGE,
|
||||
|
||||
/**
|
||||
* Moving around the target. If the target is a player, this is intended to give the player openings to attack during
|
||||
*/
|
||||
DANCING,
|
||||
|
||||
/**
|
||||
* Actually going in for an attack
|
||||
*/
|
||||
ATTACKING,
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuring tree data for this tree
|
||||
*/
|
||||
AttackerTreeData treeData;
|
||||
|
||||
|
||||
/**
|
||||
* The current state of this instance of this tree
|
||||
*/
|
||||
AttackerState state = AttackerState.IDLE;
|
||||
|
||||
/**
|
||||
* The parent to this tree
|
||||
*/
|
||||
Entity parent;
|
||||
|
||||
/**
|
||||
* The current target of this tree
|
||||
*/
|
||||
Entity target = null;
|
||||
|
||||
|
||||
/**
|
||||
* The number of frames the tree has been in its current state
|
||||
*/
|
||||
int currentStateFrameCount = 0;
|
||||
|
||||
/**
|
||||
* The random used for rolls on behavior transitions
|
||||
*/
|
||||
Random random = new Random();
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getTreeName(){
|
||||
return TREE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param parent The parent to this tree
|
||||
*/
|
||||
private Attacker(Entity parent){
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an attacker tree
|
||||
* @param parent The parent entity
|
||||
* @param treeData The tree data
|
||||
* @return The Attacker tree
|
||||
*/
|
||||
public static AITree construct(Entity parent, AttackerTreeData treeData) {
|
||||
Attacker rVal = new Attacker(parent);
|
||||
rVal.treeData = treeData;
|
||||
return rVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simulate() {
|
||||
switch(state){
|
||||
case IDLE: {
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(parent);
|
||||
if(serverBlockTree.isBlocking()){
|
||||
serverBlockTree.stop();
|
||||
}
|
||||
} break;
|
||||
case MOVING_INTO_ATTACKING_RANGE: {
|
||||
//make sure we're blocking
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(parent);
|
||||
if(!serverBlockTree.isBlocking()){
|
||||
serverBlockTree.start();
|
||||
}
|
||||
//close distance
|
||||
moveToTarget();
|
||||
} break;
|
||||
case DANCING: {
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(parent);
|
||||
if(!serverBlockTree.isBlocking()){
|
||||
serverBlockTree.start();
|
||||
}
|
||||
//TODO: move parallel
|
||||
} break;
|
||||
case ATTACKING: {
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(parent);
|
||||
ServerAttackTree serverAttackTree = ServerAttackTree.getServerAttackTree(parent);
|
||||
if(serverBlockTree.isBlocking()){
|
||||
serverBlockTree.stop();
|
||||
} else if(serverAttackTree.isAttacking()){
|
||||
|
||||
} else {
|
||||
serverAttackTree.start();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStateAndPriority() {
|
||||
switch(state){
|
||||
case IDLE: {
|
||||
if(target != null){
|
||||
setState(AttackerState.MOVING_INTO_ATTACKING_RANGE);
|
||||
} else {
|
||||
searchForTarget();
|
||||
}
|
||||
} break;
|
||||
case MOVING_INTO_ATTACKING_RANGE: {
|
||||
if(inAttackRange()){
|
||||
//should change to dancing or attacking
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.ATTACKING);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(
|
||||
inAggroRange() &&
|
||||
!inAttackRange() &&
|
||||
currentStateFrameCount > this.treeData.getStateChangeTimeout()
|
||||
){
|
||||
//should change to dancing or moving into attack range
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.MOVING_INTO_ATTACKING_RANGE);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(!inAggroRange()) {
|
||||
this.target = null;
|
||||
setState(AttackerState.IDLE);
|
||||
}
|
||||
} break;
|
||||
case DANCING: {
|
||||
if(inAttackRange()){
|
||||
//should change to dancing or attacking
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.ATTACKING);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(
|
||||
inAggroRange() &&
|
||||
!inAttackRange() &&
|
||||
currentStateFrameCount > this.treeData.getStateChangeTimeout()
|
||||
){
|
||||
//should change to dancing or moving into attack range
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.MOVING_INTO_ATTACKING_RANGE);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(!inAggroRange()) {
|
||||
this.target = null;
|
||||
setState(AttackerState.IDLE);
|
||||
}
|
||||
} break;
|
||||
case ATTACKING: {
|
||||
if(inAttackRange()){
|
||||
//should change to dancing or attacking
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.ATTACKING);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(
|
||||
inAggroRange() &&
|
||||
!inAttackRange() &&
|
||||
currentStateFrameCount > this.treeData.getStateChangeTimeout()
|
||||
){
|
||||
//should change to dancing or moving into attack range
|
||||
if(this.random.nextInt(2) > 0){
|
||||
setState(AttackerState.MOVING_INTO_ATTACKING_RANGE);
|
||||
} else {
|
||||
setState(AttackerState.DANCING);
|
||||
}
|
||||
} else if(!inAggroRange()) {
|
||||
this.target = null;
|
||||
setState(AttackerState.IDLE);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
currentStateFrameCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentStateName() {
|
||||
switch(state){
|
||||
case IDLE:
|
||||
return "IDLE";
|
||||
case MOVING_INTO_ATTACKING_RANGE:
|
||||
return "MOVING INTO ATTACKING RANGE";
|
||||
case DANCING:
|
||||
return "DANCING";
|
||||
case ATTACKING:
|
||||
return "ATTACKING";
|
||||
}
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Unaccounted for state name in ai tree"));
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentStatePriority() {
|
||||
switch(state){
|
||||
case IDLE:
|
||||
return 0;
|
||||
case MOVING_INTO_ATTACKING_RANGE:
|
||||
return 3;
|
||||
case DANCING:
|
||||
return 2;
|
||||
case ATTACKING:
|
||||
return 8;
|
||||
}
|
||||
LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Unaccounted for state name in ai tree"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the tree
|
||||
* @param state The state
|
||||
*/
|
||||
private void setState(AttackerState state){
|
||||
this.state = state;
|
||||
this.currentStateFrameCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a valid target
|
||||
*/
|
||||
private void searchForTarget(){
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the target is within the aggression range
|
||||
*/
|
||||
private boolean inAggroRange(){
|
||||
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(){
|
||||
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(){
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
package electrosphere.server.ai.creature;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.block.ServerBlockTree;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.game.data.creature.type.ai.BlockerTreeData;
|
||||
import electrosphere.server.ai.AITree;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
import electrosphere.util.math.MathUtils;
|
||||
|
||||
/**
|
||||
* AI that just continuously blocks
|
||||
*/
|
||||
public class Blocker implements AITree {
|
||||
|
||||
/**
|
||||
* The name of the tree type
|
||||
*/
|
||||
public static final String TREE_NAME = "Blocker";
|
||||
|
||||
/**
|
||||
* The configuring tree data for this tree
|
||||
*/
|
||||
BlockerTreeData treeData;
|
||||
|
||||
/**
|
||||
* The parent to this tree
|
||||
*/
|
||||
Entity parent;
|
||||
|
||||
/**
|
||||
* The current target of this tree
|
||||
*/
|
||||
Entity target = null;
|
||||
|
||||
|
||||
/**
|
||||
* The number of frames the tree has been in its current state
|
||||
*/
|
||||
int currentStateFrameCount = 0;
|
||||
|
||||
/**
|
||||
* The random used for rolls on behavior transitions
|
||||
*/
|
||||
Random random = new Random();
|
||||
|
||||
/**
|
||||
* Private constructor
|
||||
* @param parent The parent entity
|
||||
*/
|
||||
private Blocker(Entity parent){
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an attacker tree
|
||||
* @param parent The parent entity
|
||||
* @param treeData The tree data
|
||||
* @return The Attacker tree
|
||||
*/
|
||||
public static AITree construct(Entity parent, BlockerTreeData treeData) {
|
||||
Blocker rVal = new Blocker(parent);
|
||||
rVal.treeData = treeData;
|
||||
return rVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simulate() {
|
||||
if(target == null){
|
||||
searchForTarget();
|
||||
}
|
||||
if(ServerBlockTree.getServerBlockTree(parent) != null){
|
||||
ServerBlockTree serverBlockTree = ServerBlockTree.getServerBlockTree(parent);
|
||||
if(!serverBlockTree.isBlocking()){
|
||||
serverBlockTree.start();
|
||||
}
|
||||
}
|
||||
if(target != null){
|
||||
Vector3d targetPos = EntityUtils.getPosition(target);
|
||||
Quaterniond rotation = MathUtils.calculateRotationFromPointToPoint(EntityUtils.getPosition(parent), targetPos);
|
||||
EntityUtils.getRotation(parent).set(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTreeName() {
|
||||
return TREE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStateAndPriority() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentStateName() {
|
||||
return "BLCOKING";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentStatePriority() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a valid target
|
||||
*/
|
||||
private void searchForTarget(){
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Realm realm = Globals.realmManager.getEntityRealm(parent);
|
||||
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.LIFE_STATE)){
|
||||
if(current != parent && CreatureUtils.isCreature(current)){
|
||||
target = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
package electrosphere.server.ai.creature;
|
||||
|
||||
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.types.creature.CreatureUtils;
|
||||
import electrosphere.server.ai.AITree;
|
||||
|
||||
import java.util.Random;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
/**
|
||||
* An ai whose goal is to wander sporadically
|
||||
*/
|
||||
public class MillAbout implements AITree {
|
||||
|
||||
Entity character;
|
||||
|
||||
boolean millAbout = true;
|
||||
int millMoveTimer = 0;
|
||||
int millMoveTimerTimeout = 100;
|
||||
int MILL_MOVE_TIMEOUT_LOWER = 1500;
|
||||
int MILL_MOVE_TIMEOUT_UPPER = 2000;
|
||||
float millTargetMaxDist = 3;
|
||||
|
||||
boolean moveToTarget = false;
|
||||
Vector3d moveTargetPosition;
|
||||
|
||||
private MillAbout(Entity character){
|
||||
this.character = character;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void simulate(){
|
||||
Vector3d position = EntityUtils.getPosition(character);
|
||||
//mill aboud
|
||||
if(millAbout){
|
||||
if(millMoveTimer >= millMoveTimerTimeout){
|
||||
Random rand = new Random();
|
||||
millMoveTimer = 0;
|
||||
millMoveTimerTimeout = rand.nextInt(MILL_MOVE_TIMEOUT_UPPER - MILL_MOVE_TIMEOUT_LOWER) + MILL_MOVE_TIMEOUT_LOWER;
|
||||
Vector3d moveVector = new Vector3d();
|
||||
//search for a spot to mill to
|
||||
// while(true){
|
||||
moveVector.set(
|
||||
rand.nextFloat() - 0.5,
|
||||
rand.nextFloat() - 0.5,
|
||||
rand.nextFloat() - 0.5
|
||||
).normalize().mul(millTargetMaxDist);
|
||||
moveVector.y = position.y; //Globals.commonWorldData.getElevationAtPoint(new Vector3d(position).add(moveVector));
|
||||
|
||||
//TODO: search in navmeshmanager to make sure navigable, otherwise generate new pos
|
||||
// }
|
||||
moveTargetPosition = new Vector3d(position).add(moveVector);
|
||||
millAbout = false;
|
||||
moveToTarget = true;
|
||||
} else {
|
||||
millMoveTimer++;
|
||||
}
|
||||
}
|
||||
if(moveToTarget){
|
||||
if(moveTargetPosition.distance(position) > 0.4){
|
||||
Vector3d moveVector = new Vector3d(moveTargetPosition).sub(position).normalize();
|
||||
CreatureUtils.setFacingVector(character, new Vector3d((float)moveVector.x,(float)moveVector.y,(float)moveVector.z));
|
||||
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(character);
|
||||
if(characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.IDLE || characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.SLOWDOWN){
|
||||
characterMoveTree.start(MovementRelativeFacing.FORWARD);
|
||||
}
|
||||
} else {
|
||||
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(character);
|
||||
characterMoveTree.slowdown();
|
||||
// System.out.println("Made it to destination");
|
||||
moveToTarget = false;
|
||||
millAbout = true;
|
||||
}
|
||||
}
|
||||
// if(target != null){
|
||||
// if(inAttackRange()){
|
||||
// attack();
|
||||
// } else {
|
||||
// if(inAggroRange()){
|
||||
// moveToTarget();
|
||||
// } else {
|
||||
// target = null;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// searchForTarget();
|
||||
// }
|
||||
}
|
||||
|
||||
public static AITree construct(Entity parent) {
|
||||
MillAbout rVal = new MillAbout(parent);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTreeName() {
|
||||
return "MillAbout";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStateAndPriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'updateStateAndPriority'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentStateName() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStateName'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentStatePriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStatePriority'");
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
package electrosphere.server.ai.creature;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.attack.ServerAttackTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.server.ai.AITree;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
/**
|
||||
* An ai whose goal is to find a weapon and try to kill nearby enemies
|
||||
*/
|
||||
public class MindlessAttacker implements AITree {
|
||||
|
||||
|
||||
/**
|
||||
* The parent entity to this ai tree
|
||||
*/
|
||||
Entity parent;
|
||||
|
||||
/**
|
||||
* The current target of this tree
|
||||
*/
|
||||
Entity target;
|
||||
|
||||
//aggression ranges
|
||||
float aggroRange = 10.0f;
|
||||
float attackRange = 1.0f;
|
||||
|
||||
//cooldown controls
|
||||
int attackCooldownMax = 250;
|
||||
int attackCooldown = 0;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void simulate(){
|
||||
if(target != null){
|
||||
if(inAttackRange()){
|
||||
attack();
|
||||
} else {
|
||||
if(inAggroRange()){
|
||||
moveToTarget();
|
||||
} else {
|
||||
target = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
searchForTarget();
|
||||
}
|
||||
}
|
||||
|
||||
void attack(){
|
||||
if(attackCooldown == 0){
|
||||
attackCooldown = attackCooldownMax;
|
||||
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(parent);
|
||||
attackTree.start();
|
||||
} else {
|
||||
attackCooldown--;
|
||||
}
|
||||
}
|
||||
|
||||
void moveToTarget(){
|
||||
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));
|
||||
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(parent);
|
||||
if(characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.IDLE || characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.SLOWDOWN){
|
||||
characterMoveTree.start(MovementRelativeFacing.FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
boolean inAttackRange(){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < attackRange){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
boolean inAggroRange(){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < aggroRange){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
void searchForTarget(){
|
||||
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){
|
||||
target = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static AITree construct(Entity parent) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'construct'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void updateStateAndPriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'updateStateAndPriority'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getCurrentStateName() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStateName'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int getCurrentStatePriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStatePriority'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTreeName(){
|
||||
return "MindlessAttacker";
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,315 +0,0 @@
|
||||
package electrosphere.server.ai.creature;
|
||||
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityTags;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.state.attack.ServerAttackTree;
|
||||
import electrosphere.entity.state.equip.ClientEquipState;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
|
||||
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.entity.types.item.ItemUtils;
|
||||
import electrosphere.server.ai.AITree;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.utils.DataCellSearchUtils;
|
||||
import electrosphere.util.math.MathUtils;
|
||||
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
/**
|
||||
* An ai whose goal is to try to kill enemies; however, if it has no access to a weapon it will try to run away
|
||||
*/
|
||||
public class OpportunisticAttacker implements AITree {
|
||||
|
||||
/**
|
||||
* The parent of this ai tree
|
||||
*/
|
||||
Entity parent;
|
||||
|
||||
/**
|
||||
* The target for this ai
|
||||
*/
|
||||
Entity target;
|
||||
|
||||
float aggroRange = 10.0f;
|
||||
float attackRange = 1.0f;
|
||||
|
||||
float weaponSeekRange = 10.0f;
|
||||
float pickupRange = 2.0f;
|
||||
|
||||
int attackCooldownMax = 250;
|
||||
int attackCooldown = 0;
|
||||
|
||||
enum OpportunisticAttackerGoal {
|
||||
ATTACK,
|
||||
SEEK_WEAPON,
|
||||
FLEE,
|
||||
IDLE,
|
||||
}
|
||||
|
||||
OpportunisticAttackerGoal goal;
|
||||
|
||||
@Override
|
||||
public String getTreeName(){
|
||||
return "OpportunisticAttacker";
|
||||
}
|
||||
|
||||
OpportunisticAttacker(Entity character){
|
||||
this.parent = character;
|
||||
}
|
||||
|
||||
public static void attachToCreature(Entity creature){
|
||||
OpportunisticAttacker ai = new OpportunisticAttacker(creature);
|
||||
ai.goal = OpportunisticAttackerGoal.IDLE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void simulate(){
|
||||
switch(goal){
|
||||
case ATTACK:
|
||||
if(target != null){
|
||||
if(inAttackRange()){
|
||||
if(hasWeapon()){
|
||||
faceTarget();
|
||||
attack();
|
||||
} else {
|
||||
if(weaponInRange()){
|
||||
setGoal(OpportunisticAttackerGoal.SEEK_WEAPON);
|
||||
} else {
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(inAggroRange()){
|
||||
if(hasWeapon()){
|
||||
faceTarget();
|
||||
moveToTarget();
|
||||
} else {
|
||||
if(weaponInRange()){
|
||||
setGoal(OpportunisticAttackerGoal.SEEK_WEAPON);
|
||||
} else {
|
||||
setGoal(OpportunisticAttackerGoal.FLEE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
target = null;
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
break;
|
||||
case SEEK_WEAPON:
|
||||
if(target != null){
|
||||
if(weaponInRange()){
|
||||
if(weaponInPickupRange()){
|
||||
pickupWeapon();
|
||||
searchForTarget();
|
||||
if(target != null){
|
||||
setGoal(OpportunisticAttackerGoal.ATTACK);
|
||||
}
|
||||
} else {
|
||||
moveToTarget();
|
||||
}
|
||||
} else {
|
||||
setGoal(OpportunisticAttackerGoal.FLEE);
|
||||
}
|
||||
} else {
|
||||
if(inAggroRange()){
|
||||
if(hasWeapon()){
|
||||
moveToTarget();
|
||||
} else {
|
||||
if(!weaponInRange()){
|
||||
setGoal(OpportunisticAttackerGoal.FLEE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
target = null;
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FLEE:
|
||||
if(target != null){
|
||||
if(weaponInRange()){
|
||||
setGoal(OpportunisticAttackerGoal.SEEK_WEAPON);
|
||||
} else {
|
||||
if(inAggroRange()){
|
||||
//find direction opposite target and move there
|
||||
} else {
|
||||
target = null;
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setGoal(OpportunisticAttackerGoal.IDLE);
|
||||
}
|
||||
break;
|
||||
case IDLE:
|
||||
searchForTarget();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void attack(){
|
||||
if(attackCooldown == 0){
|
||||
attackCooldown = attackCooldownMax;
|
||||
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(parent);
|
||||
attackTree.start();
|
||||
} else {
|
||||
attackCooldown--;
|
||||
}
|
||||
}
|
||||
|
||||
void moveToTarget(){
|
||||
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));
|
||||
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(parent);
|
||||
if(characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.IDLE || characterMoveTree.getState()==ClientGroundMovementTree.MovementTreeState.SLOWDOWN){
|
||||
characterMoveTree.start(MovementRelativeFacing.FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
boolean inAttackRange(){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < attackRange){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
boolean inAggroRange(){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
if(new Vector3d(position).distance(targetPosition) < aggroRange){
|
||||
rVal = true;
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
void searchForTarget(){
|
||||
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){
|
||||
target = current;
|
||||
setGoal(OpportunisticAttackerGoal.ATTACK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasWeapon(){
|
||||
boolean rVal = false;
|
||||
if(ClientEquipState.hasEquipState(parent)){
|
||||
//TODO:fix hasWeapon
|
||||
ClientEquipState equipState = ClientEquipState.getEquipState(parent);
|
||||
// if(equipState.hasEquipPrimary()){
|
||||
// rVal = true;
|
||||
// }
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
boolean weaponInRange(){
|
||||
boolean rVal = false;
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Realm realm = Globals.realmManager.getEntityRealm(parent);
|
||||
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.ITEM)){
|
||||
if(current != parent && ItemUtils.isItem(current) && ItemUtils.isWeapon(current)){
|
||||
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
|
||||
if(position.distance(potentialTargetPosition) < weaponSeekRange){
|
||||
target = current;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
boolean weaponInPickupRange(){
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
if(target != null){
|
||||
Vector3d potentialTargetPosition = EntityUtils.getPosition(target);
|
||||
if(position.distance(potentialTargetPosition) < pickupRange){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void pickupWeapon(){
|
||||
if(ClientEquipState.hasEquipState(parent)){
|
||||
ClientEquipState equipState = ClientEquipState.getEquipState(parent);
|
||||
// if(!equipState.hasEquipPrimary()){
|
||||
// equipState.attemptEquip(target);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void faceTarget(){
|
||||
Vector3d position = EntityUtils.getPosition(parent);
|
||||
Vector3d targetPosition = EntityUtils.getPosition(target);
|
||||
Vector3d movementVector = new Vector3d(targetPosition).sub(position).normalize();
|
||||
Quaterniond movementQuaternion = new Quaterniond().rotationTo(MathUtils.getOriginVector(), new Vector3d(movementVector.x,0,movementVector.z)).normalize();
|
||||
CreatureUtils.setFacingVector(parent, movementVector);
|
||||
EntityUtils.getRotation(parent).set(movementQuaternion);
|
||||
}
|
||||
|
||||
void setGoal(OpportunisticAttackerGoal goal){
|
||||
this.goal = goal;
|
||||
switch(goal){
|
||||
case IDLE:
|
||||
// System.out.println("Set goal idle");
|
||||
break;
|
||||
case FLEE:
|
||||
// System.out.println("Set goal flee");
|
||||
break;
|
||||
case SEEK_WEAPON:
|
||||
// System.out.println("Set goal seek weapon");
|
||||
break;
|
||||
case ATTACK:
|
||||
// System.out.println("Set goal attack");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static AITree construct(Entity parent) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'construct'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStateAndPriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'updateStateAndPriority'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentStateName() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStateName'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentStatePriority() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getCurrentStatePriority'");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
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,84 @@
|
||||
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,24 @@
|
||||
package electrosphere.server.ai.trees.creature;
|
||||
|
||||
import electrosphere.server.ai.AITreeNode;
|
||||
import electrosphere.server.ai.nodes.combat.BlockNode;
|
||||
|
||||
/**
|
||||
* Figures out how if it should attack and how it can, then executes that subtree
|
||||
*/
|
||||
public class AttackerAITree {
|
||||
|
||||
/**
|
||||
* Name of the tree
|
||||
*/
|
||||
public static final String TREE_NAME = "Attacker";
|
||||
|
||||
/**
|
||||
* Creates an attacker ai tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(){
|
||||
return new BlockNode(null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Creates a blocker test tree
|
||||
*/
|
||||
public class BlockerAITree {
|
||||
|
||||
/**
|
||||
* Name of the tree
|
||||
*/
|
||||
public static final String TREE_NAME = "Blocker";
|
||||
|
||||
/**
|
||||
* Creates a blocker tree
|
||||
* @return The root node of the tree
|
||||
*/
|
||||
public static AITreeNode create(BlockerTreeData data){
|
||||
return new BlockNode(null);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user