Basic ai framework
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-07-21 16:46:25 -04:00
parent c776dae868
commit b10b7e8407
23 changed files with 1049 additions and 248 deletions

View File

@ -408,7 +408,15 @@
"cameraViewDirOffsetY" : -0.3,
"cameraViewDirOffsetZ" : 0.0,
"firstPersonModelPath" : "Models/creatures/viewmodel.glb"
}
},
"aiTrees" : [
{
"name" : "Attacker",
"aggroRange" : 10,
"attackRange" : 2,
"stateChangeTimeout" : "240"
}
]
}
],
"files" : []

View File

@ -25,7 +25,5 @@ export const ENGINE_onInit = () => {
engine.sceneLoader.engine = engine
engine.hookManager.engine = engine
console.log(Object.keys(engine.classes))
loggerScripts.INFO('Script Engine Initialized')
}

View File

@ -31,15 +31,26 @@ public class LevelEditorLoading {
if(params.length < 1){
throw new IllegalStateException("Trying to load level editor with insufficient params");
}
LevelDescription description = (LevelDescription)params[0];
String saveName = description.getName();
SceneFile sceneFile = description.getSceneFile();
String saveName = null;
SceneFile sceneFile = null;
//figure out scene stuff
if(params[0] instanceof LevelDescription){
//fires when creating a level for the first time
LevelDescription description = (LevelDescription)params[0];
saveName = description.getName();
sceneFile= description.getSceneFile();
} else {
//fires when subsequently editing
saveName = (String)params[0];
}
//
//Set params we would expect to run with this thread
//
Globals.RUN_CLIENT = true;
Globals.RUN_SERVER = true;
Globals.aiManager.setActive(false);
Window loadingWindow = (Window)Globals.elementManager.getWindow(WindowStrings.WINDOW_LOADING);

View File

@ -281,6 +281,7 @@ public class EntityDataStrings {
AI stuff
*/
public static final String VIEW_PITCH = "aiViewPitch";
public static final String AI = "ai";
/**
* Pose actor

View File

@ -454,6 +454,14 @@ public class ServerAttackTree implements BehaviorTree {
parent.putData(EntityDataStrings.ATTACK_MOVE_TYPE_ACTIVE, attackType);
}
/**
* Checks whether the attack tree is active or not
* @return true if active, false otherwise
*/
public boolean isAttacking(){
return this.state != AttackTreeState.IDLE;
}
/**
* Gets the current moveset
* @param attackType the attack type

View File

@ -103,6 +103,14 @@ public class ServerBlockTree implements BehaviorTree {
}
}
/**
* Gets whether the tree is blocking or not
* @return true if blocking, false otherwise
*/
public boolean isBlocking(){
return this.state == BlockState.BLOCKING;
}
/**
* Gets the block system data for this tree
* @return the data if it exists, otherwise null

View File

@ -650,6 +650,16 @@ public class CreatureUtils {
rVal.putData(EntityDataStrings.SERVER_ROTATOR_TREE, rotatorTree);
ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, rotatorTree);
}
///
///
/// AI (This SHOULD only be applied on the server with the way AI architected currently)
///
///
if(rawType.getAITrees() != null){
Globals.aiManager.attachAI(rVal, rawType.getAITrees());
}
//add health system
rVal.putData(EntityDataStrings.LIFE_STATE, new LifeState(rVal, rawType.getHealthSystem()));
ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.LIFE_STATE);

View File

@ -2,6 +2,7 @@ package electrosphere.game.data.creature.type;
import electrosphere.game.data.collidable.CollidableTemplate;
import electrosphere.game.data.collidable.HitboxData;
import electrosphere.game.data.creature.type.ai.AITreeData;
import electrosphere.game.data.creature.type.attack.AttackMove;
import electrosphere.game.data.creature.type.attack.AttackMoveResolver;
import electrosphere.game.data.creature.type.block.BlockSystem;
@ -16,90 +17,234 @@ import java.util.List;
* A given type of creature
*/
public class CreatureType {
/**
* The id of the creature
*/
String creatureId;
/**
* The list of hitboxes on the creature
*/
List<HitboxData> hitboxes;
/**
* Various tokens
*/
List<String> tokens;
/**
* The visual attributes that can be configured on this creature type
*/
List<VisualAttribute> visualAttributes;
/**
* The movement systems available to this creature type
*/
List<MovementSystem> movementSystems;
/**
* Rotator systems available to this creature type
*/
RotatorSystem rotatorSystem;
/**
* The list of equip points on this creature
*/
List<EquipPoint> equipPoints;
/**
* The collidable used for this creature type
*/
CollidableTemplate collidable;
/**
* The list of attack moves available to this creature
*/
List<AttackMove> attackMoves;
/**
* The health system available to this creature
*/
HealthSystem healthSystem;
/**
* The look at system available for this creature
*/
LookAtSystem lookAtSystem;
/**
* The model path for this creature
*/
String modelPath;
/**
* The view model data for this creature
*/
ViewModelData viewModelData;
/**
* The idle data for this creature
*/
IdleData idleData;
/**
* The block system for this creature
*/
BlockSystem blockSystem;
/**
* The configuration data for the ai trees associated with this creature
*/
List<AITreeData> aiTrees;
/**
* The attack move resolver for this creature type
*/
AttackMoveResolver attackMoveResolver;
/**
* Gets the id for this creature type
* @return The id
*/
public String getCreatureId() {
return creatureId;
}
/**
* Gets the list of hitboxes for this creature
* @return The list of hitbox data
*/
public List<HitboxData> getHitboxes() {
return hitboxes;
}
/**
* Gets the tokens for this creature
* @return The tokens
*/
public List<String> getTokens() {
return tokens;
}
/**
* Gets the configurable visual attributes for this creature type
* @return The list of visual attribute data
*/
public List<VisualAttribute> getVisualAttributes(){
return visualAttributes;
}
/**
* Gets the path for the model for this creature
* @return The model path
*/
public String getModelPath() {
return modelPath;
}
/**
* Gets the list of data of movement types available to this creature
* @return The list of movement type data
*/
public List<MovementSystem> getMovementSystems() {
return movementSystems;
}
/**
* Gets the list of attack moves available to this creature type
* @return The list of attack moves
*/
public List<AttackMove> getAttackMoves() {
return attackMoves;
}
/**
* Gets the health system data for this creature type
* @return The health system data
*/
public HealthSystem getHealthSystem() {
return healthSystem;
}
/**
* Gets the collidable data for this creature type
* @return The collidable data
*/
public CollidableTemplate getCollidable() {
return collidable;
}
/**
* Gets the look at system configuration for this creature type
* @return The look at system data
*/
public LookAtSystem getLookAtSystem() {
return lookAtSystem;
}
/**
* Gets the rotator data for this creature type
* @return The rotator data
*/
public RotatorSystem getRotatorSystem() {
return rotatorSystem;
}
/**
* Gets the list of equip points for this creature type
* @return The list of equip points
*/
public List<EquipPoint> getEquipPoints(){
return equipPoints;
}
/**
* Sets the attack move resolver for this creature type
* @param resolver The resolver
*/
public void setAttackMoveResolver(AttackMoveResolver resolver){
attackMoveResolver = resolver;
}
/**
* Gets the attack move resolver for this creature type
* @return The attack move resolver
*/
public AttackMoveResolver getAttackMoveResolver(){
return attackMoveResolver;
}
/**
* Gets the first-person view model data for this creature type
* @return The first-person view model data
*/
public ViewModelData getViewModelData(){
return viewModelData;
}
/**
* Gets the idle data for this creature type (ie animation data)
* @return The idle data
*/
public IdleData getIdleData(){
return idleData;
}
/**
* Gets the block system data for this creature type
* @return The block system data
*/
public BlockSystem getBlockSystem(){
return blockSystem;
}
/**
* Gets the AI tree data associated with this creature type
* @return The list of ai tree data
*/
public List<AITreeData> getAITrees(){
return aiTrees;
}

View File

@ -0,0 +1,13 @@
package electrosphere.game.data.creature.type.ai;
/**
* Configuration data for an ai tree
*/
public interface AITreeData {
/**
* Gets the name of the tree type
*/
public String getName();
}

View File

@ -0,0 +1,30 @@
package electrosphere.game.data.creature.type.ai;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.ai.creature.Attacker;
/**
* Deserializes ai tree data types
*/
public class AITreeDataSerializer implements JsonDeserializer<AITreeData> {
@Override
public AITreeData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
switch(json.getAsJsonObject().get("name").getAsString()){
case Attacker.TREE_NAME:
return context.deserialize(json, AttackerTreeData.class);
}
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("JSON Object provided to AITreeDataSerializer that cannot deserialize into a tree data type cleanly"));
return null;
}
}

View File

@ -0,0 +1,52 @@
package electrosphere.game.data.creature.type.ai;
/**
* Configuration data for an attacker tree
*/
public class AttackerTreeData implements AITreeData {
/**
* The range at which this entity will locate enemies
*/
float aggroRange;
/**
* The range at which attack animations will be attempted
*/
float attackRange;
/**
* The number of frames to wait before changing between states
*/
int stateChangeTimeout;
/**
* Gets the range at which this entity will locate enemies
* @return The range
*/
public float getAggroRange(){
return aggroRange;
}
/**
* Gets the range at which attack animations will be attempted
* @return The range
*/
public float getAttackRange(){
return attackRange;
}
/**
* Gets the number of frames to wait before changing between states
* @return The number of frames
*/
public int getStateChangeTimeout(){
return stateChangeTimeout;
}
@Override
public String getName() {
return "Attacker";
}
}

View File

@ -95,10 +95,21 @@ public class MenuGeneratorsLevelEditor {
loadingThread.start();
});
//
//button (edit Level)
//
Button editButton = Button.createButton("Edit", () -> {
//launch level editor
LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL_EDITOR, saveName);
Globals.loadingThreadsList.add(loadingThread);
loadingThread.start();
});
//create row
Div row = Div.createRow(
deleteButton,
launchButton
launchButton,
editButton
);
row.setMaxHeight(30);
existingLevelColumn.addChild(row);

View File

@ -1,10 +1,137 @@
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.logger.LoggerInterface;
import electrosphere.server.ai.creature.Attacker;
/**
* An ai
* A collection of AITrees that are attached to a single entity.
* Maintains which
*/
public abstract class AI {
public abstract void simulate();
public class AI {
/**
* The error checking threshold for ai mutation
*/
static final float ERROR_CHECK_ROT_THRESHOLD = 0.999f;
/**
* The entity this ai is associated with
*/
Entity parent;
/**
* The list of trees for this ai in particular
*/
List<AITree> trees = new LinkedList<AITree>();
/**
* 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
* @return The AI
*/
protected static AI constructAI(Entity parent, List<AITreeData> treeData){
AI rVal = new AI(parent);
//attach all trees
for(AITreeData aiData : treeData){
switch(aiData.getName()){
case Attacker.TREE_NAME:
rVal.trees.add(Attacker.construct(parent, (AttackerTreeData) aiData));
break;
}
}
return rVal;
}
/**
* Private constructor
*/
private AI(Entity parent){
this.parent = parent;
}
/**
* 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));
//
//Update state and find the highest priority tree
AITree currentTree = null;
for(AITree tree : trees){
tree.updateStateAndPriority();
if(currentTree == null){
currentTree = tree;
} else if(currentTree.getCurrentStatePriority() < tree.getCurrentStatePriority()){
currentTree = tree;
}
}
//
//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();
}
}
/**
* Sets the ai key on the entity
* @param entity The entity
* @param ai The ai
*/
public static void setAI(Entity entity, AI ai){
entity.putData(EntityDataStrings.AI, ai);
}
/**
* Gets the ai on the key of the entity
* @param entity The entity
* @return The AI if it exists, null otherwise
*/
public static AI getAI(Entity entity){
if(entity.containsKey(EntityDataStrings.AI)){
return (AI)entity.getData(EntityDataStrings.AI);
} else {
return null;
}
}
}

View File

@ -1,35 +1,97 @@
package electrosphere.server.ai;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import electrosphere.entity.Entity;
import electrosphere.game.data.creature.type.ai.AITreeData;
import electrosphere.logger.LoggerInterface;
/**
* Server manager for all entity AIs
*/
public class AIManager {
/**
* The list of ais
*/
List<AI> aiList = new LinkedList<AI>();
/**
* The map of ai to associated entity
*/
Map<AI,Entity> aiEntityMap = new HashMap<AI,Entity>();
/**
* The map of entity to associated ai
*/
Map<Entity,AI> entityAIMap = new HashMap<Entity,AI>();
/**
* Controls whether the ai manager should simulate each frame or not
*/
boolean active = true;
/**
* Constructor
*/
public AIManager(){
}
/**
* Simulates all AIs currently available
*/
public void simulate(){
for(AI ai : aiList){
ai.simulate();
if(isActive()){
for(AI ai : aiList){
ai.simulate();
}
}
}
public void registerAI(AI ai){
if(!aiList.contains(ai)){
aiList.add(ai);
}
/**
* Sets the active status of the ai manager
* @param isActive true to simulate ai each frame, false otherwise
*/
public void setActive(boolean isActive){
this.active = isActive;
}
public void deregisterAI(AI ai){
if(aiList.contains(ai)){
aiList.remove(ai);
/**
* Gets whether the ai manager is active or not
* @return true if simulating each frame, false otherwise
*/
public boolean isActive(){
return active;
}
/**
* Attaches an AI to an entity
* @param entity The entity
* @param treeData The list of data on trees to be provided
*/
public void attachAI(Entity entity, List<AITreeData> treeData){
if(entity == null){
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Entity provided is null!"));
}
AI ai = AI.constructAI(entity, treeData);
aiList.add(ai);
entityAIMap.put(entity,ai);
aiEntityMap.put(ai,entity);
}
/**
* Removes the ai for this entity from the manager
* @param entity The entity
*/
public void removeAI(Entity entity){
AI targetAI = entityAIMap.get(entity);
aiList.remove(targetAI);
aiEntityMap.remove(targetAI);
entityAIMap.remove(entity);
}
}

View File

@ -0,0 +1,40 @@
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();
}

View File

@ -0,0 +1,344 @@
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);
}
}
}

View File

@ -1,12 +1,11 @@
package electrosphere.server.ai.creature;
import electrosphere.engine.Globals;
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.AI;
import electrosphere.server.ai.AITree;
import java.util.Random;
import org.joml.Vector3d;
@ -14,16 +13,10 @@ import org.joml.Vector3d;
/**
* An ai whose goal is to wander sporadically
*/
public class MillAbout extends AI {
public class MillAbout implements AITree {
Entity character;
// float aggroRange = 10.0f;
// float attackRange = 1.0f;
//
// int attackCooldownMax = 250;
// int attackCooldown = 0;
boolean millAbout = true;
int millMoveTimer = 0;
int millMoveTimerTimeout = 100;
@ -34,15 +27,10 @@ public class MillAbout extends AI {
boolean moveToTarget = false;
Vector3d moveTargetPosition;
public MillAbout(Entity character){
private MillAbout(Entity character){
this.character = character;
}
public static void attachToCreature(Entity creature){
MillAbout ai = new MillAbout(creature);
Globals.aiManager.registerAI(ai);
}
@Override
@ -103,60 +91,33 @@ public class MillAbout extends AI {
// searchForTarget();
// }
}
// void attack(){
// if(attackCooldown == 0){
// attackCooldown = attackCooldownMax;
// AttackTree attackTree = CreatureUtils.getAttackTree(character);
// attackTree.start(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND);
// } else {
// attackCooldown--;
// }
// }
// void moveToTarget(){
// Vector3d targetPosition = EntityUtils.getPosition(target);
// Vector3d characterPosition = EntityUtils.getPosition(character);
// Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).normalize();
// CreatureUtils.setMovementVector(character, new Vector3f((float)moveVector.x,(float)moveVector.y,(float)moveVector.z));
// GroundMovementTree characterMoveTree = CreatureUtils.getEntityMovementTree(character);
// if(characterMoveTree.getState()==GroundMovementTree.MovementTreeState.IDLE || characterMoveTree.getState()==GroundMovementTree.MovementTreeState.SLOWDOWN){
// characterMoveTree.start();
// }
// }
// boolean inAttackRange(){
// boolean rVal = false;
// Vector3d position = EntityUtils.getPosition(character);
// 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(character);
// Vector3d targetPosition = EntityUtils.getPosition(target);
// if(new Vector3d(position).distance(targetPosition) < aggroRange){
// rVal = true;
// }
// return rVal;
// }
// void searchForTarget(){
// Vector3d position = EntityUtils.getPosition(character);
// for(Entity current : Globals.entityManager.getLifeStateEntities()){
// if(current != character){
// Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
// if(position.distance(potentialTargetPosition) < aggroRange){
// target = current;
// break;
// }
// }
// }
// }
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'");
}
}

View File

@ -8,7 +8,7 @@ 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.AI;
import electrosphere.server.ai.AITree;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.DataCellSearchUtils;
@ -17,27 +17,27 @@ import org.joml.Vector3d;
/**
* An ai whose goal is to find a weapon and try to kill nearby enemies
*/
public class MindlessAttacker extends AI{
public class MindlessAttacker implements AITree {
Entity character;
/**
* 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;
public MindlessAttacker(Entity character){
this.character = character;
}
public static void attachToCreature(Entity creature){
MindlessAttacker ai = new MindlessAttacker(creature);
Globals.aiManager.registerAI(ai);
}
@Override
@ -60,7 +60,7 @@ public class MindlessAttacker extends AI{
void attack(){
if(attackCooldown == 0){
attackCooldown = attackCooldownMax;
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(character);
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(parent);
attackTree.start();
} else {
attackCooldown--;
@ -69,10 +69,10 @@ public class MindlessAttacker extends AI{
void moveToTarget(){
Vector3d targetPosition = EntityUtils.getPosition(target);
Vector3d characterPosition = EntityUtils.getPosition(character);
Vector3d characterPosition = EntityUtils.getPosition(parent);
Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).normalize();
CreatureUtils.setFacingVector(character, new Vector3d((float)moveVector.x,(float)moveVector.y,(float)moveVector.z));
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(character);
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);
}
@ -80,7 +80,7 @@ public class MindlessAttacker extends AI{
boolean inAttackRange(){
boolean rVal = false;
Vector3d position = EntityUtils.getPosition(character);
Vector3d position = EntityUtils.getPosition(parent);
Vector3d targetPosition = EntityUtils.getPosition(target);
if(new Vector3d(position).distance(targetPosition) < attackRange){
rVal = true;
@ -90,7 +90,7 @@ public class MindlessAttacker extends AI{
boolean inAggroRange(){
boolean rVal = false;
Vector3d position = EntityUtils.getPosition(character);
Vector3d position = EntityUtils.getPosition(parent);
Vector3d targetPosition = EntityUtils.getPosition(target);
if(new Vector3d(position).distance(targetPosition) < aggroRange){
rVal = true;
@ -100,10 +100,10 @@ public class MindlessAttacker extends AI{
void searchForTarget(){
Vector3d position = EntityUtils.getPosition(character);
Realm realm = Globals.realmManager.getEntityRealm(character);
Vector3d position = EntityUtils.getPosition(parent);
Realm realm = Globals.realmManager.getEntityRealm(parent);
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.LIFE_STATE)){
if(current != character){
if(current != parent){
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
if(position.distance(potentialTargetPosition) < aggroRange){
target = current;
@ -112,5 +112,41 @@ public class MindlessAttacker extends AI{
}
}
}
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";
}
}

View File

@ -10,7 +10,7 @@ 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.AI;
import electrosphere.server.ai.AITree;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.DataCellSearchUtils;
import electrosphere.util.MathUtils;
@ -21,11 +21,16 @@ 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 extends AI {
public class OpportunisticAttacker implements AITree {
/**
* The parent of this ai tree
*/
Entity parent;
Entity character;
/**
* The target for this ai
*/
Entity target;
float aggroRange = 10.0f;
@ -45,15 +50,19 @@ public class OpportunisticAttacker extends AI {
}
OpportunisticAttackerGoal goal;
@Override
public String getTreeName(){
return "OpportunisticAttacker";
}
OpportunisticAttacker(Entity character){
this.character = character;
this.parent = character;
}
public static void attachToCreature(Entity creature){
OpportunisticAttacker ai = new OpportunisticAttacker(creature);
ai.goal = OpportunisticAttackerGoal.IDLE;
Globals.aiManager.registerAI(ai);
}
@ -151,7 +160,7 @@ public class OpportunisticAttacker extends AI {
void attack(){
if(attackCooldown == 0){
attackCooldown = attackCooldownMax;
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(character);
ServerAttackTree attackTree = CreatureUtils.serverGetAttackTree(parent);
attackTree.start();
} else {
attackCooldown--;
@ -160,10 +169,10 @@ public class OpportunisticAttacker extends AI {
void moveToTarget(){
Vector3d targetPosition = EntityUtils.getPosition(target);
Vector3d characterPosition = EntityUtils.getPosition(character);
Vector3d characterPosition = EntityUtils.getPosition(parent);
Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).normalize();
CreatureUtils.setFacingVector(character, new Vector3d((float)moveVector.x,(float)moveVector.y,(float)moveVector.z));
ClientGroundMovementTree characterMoveTree = (ClientGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(character);
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);
}
@ -171,7 +180,7 @@ public class OpportunisticAttacker extends AI {
boolean inAttackRange(){
boolean rVal = false;
Vector3d position = EntityUtils.getPosition(character);
Vector3d position = EntityUtils.getPosition(parent);
Vector3d targetPosition = EntityUtils.getPosition(target);
if(new Vector3d(position).distance(targetPosition) < attackRange){
rVal = true;
@ -181,7 +190,7 @@ public class OpportunisticAttacker extends AI {
boolean inAggroRange(){
boolean rVal = false;
Vector3d position = EntityUtils.getPosition(character);
Vector3d position = EntityUtils.getPosition(parent);
Vector3d targetPosition = EntityUtils.getPosition(target);
if(new Vector3d(position).distance(targetPosition) < aggroRange){
rVal = true;
@ -191,10 +200,10 @@ public class OpportunisticAttacker extends AI {
void searchForTarget(){
Vector3d position = EntityUtils.getPosition(character);
Realm realm = Globals.realmManager.getEntityRealm(character);
Vector3d position = EntityUtils.getPosition(parent);
Realm realm = Globals.realmManager.getEntityRealm(parent);
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.LIFE_STATE)){
if(current != character){
if(current != parent){
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
if(position.distance(potentialTargetPosition) < aggroRange){
target = current;
@ -207,9 +216,9 @@ public class OpportunisticAttacker extends AI {
boolean hasWeapon(){
boolean rVal = false;
if(ClientEquipState.hasEquipState(character)){
if(ClientEquipState.hasEquipState(parent)){
//TODO:fix hasWeapon
ClientEquipState equipState = ClientEquipState.getEquipState(character);
ClientEquipState equipState = ClientEquipState.getEquipState(parent);
// if(equipState.hasEquipPrimary()){
// rVal = true;
// }
@ -219,10 +228,10 @@ public class OpportunisticAttacker extends AI {
boolean weaponInRange(){
boolean rVal = false;
Vector3d position = EntityUtils.getPosition(character);
Realm realm = Globals.realmManager.getEntityRealm(character);
Vector3d position = EntityUtils.getPosition(parent);
Realm realm = Globals.realmManager.getEntityRealm(parent);
for(Entity current : DataCellSearchUtils.getEntitiesWithTagAroundLocation(realm,position,EntityTags.ITEM)){
if(current != character && ItemUtils.isItem(current) && ItemUtils.isWeapon(current)){
if(current != parent && ItemUtils.isItem(current) && ItemUtils.isWeapon(current)){
Vector3d potentialTargetPosition = EntityUtils.getPosition(current);
if(position.distance(potentialTargetPosition) < weaponSeekRange){
target = current;
@ -234,7 +243,7 @@ public class OpportunisticAttacker extends AI {
}
boolean weaponInPickupRange(){
Vector3d position = EntityUtils.getPosition(character);
Vector3d position = EntityUtils.getPosition(parent);
if(target != null){
Vector3d potentialTargetPosition = EntityUtils.getPosition(target);
if(position.distance(potentialTargetPosition) < pickupRange){
@ -245,8 +254,8 @@ public class OpportunisticAttacker extends AI {
}
void pickupWeapon(){
if(ClientEquipState.hasEquipState(character)){
ClientEquipState equipState = ClientEquipState.getEquipState(character);
if(ClientEquipState.hasEquipState(parent)){
ClientEquipState equipState = ClientEquipState.getEquipState(parent);
// if(!equipState.hasEquipPrimary()){
// equipState.attemptEquip(target);
// }
@ -254,12 +263,12 @@ public class OpportunisticAttacker extends AI {
}
void faceTarget(){
Vector3d position = EntityUtils.getPosition(character);
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(character, movementVector);
EntityUtils.getRotation(character).set(movementQuaternion);
CreatureUtils.setFacingVector(parent, movementVector);
EntityUtils.getRotation(parent).set(movementQuaternion);
}
void setGoal(OpportunisticAttackerGoal goal){
@ -279,5 +288,28 @@ public class OpportunisticAttacker extends AI {
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'");
}
}

View File

@ -1,58 +0,0 @@
package electrosphere.server.ai.creature.adventurer;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
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.game.server.town.Town;
import electrosphere.server.ai.AI;
public class SeekTown extends AI {
Entity character;
Town target = null;
Vector3d targetPos = null;
public SeekTown(Entity character){
this.character = character;
}
public static void attachToCreature(Entity creature){
SeekTown ai = new SeekTown(creature);
Globals.aiManager.registerAI(ai);
}
@Override
public void simulate() {
// if(target == null){
// if(Globals.macroData.getTowns().size() > 0){
// target = Globals.macroData.getTowns().get(0);
// targetPos = new Vector3d(
// Globals.commonWorldData.convertWorldToReal(target.getPositions().get(0).x),
// 0,
// Globals.commonWorldData.convertWorldToReal(target.getPositions().get(0).y)
// );
// targetPos.y = Globals.commonWorldData.getElevationAtPoint(targetPos);
// System.out.println("Target pos: " + targetPos);
// }
// }
moveToTarget();
}
void moveToTarget(){
Vector3d targetPosition = targetPos;
Vector3d characterPosition = EntityUtils.getPosition(character);
Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).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);
}
}
}

View File

@ -1,51 +0,0 @@
package electrosphere.server.ai.creature.party;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
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.AI;
public class PartyFollower extends AI {
Entity character;
Entity target;
double threshold = 0;
public PartyFollower(Entity character, Entity target, double threshold){
this.character = character;
this.target = target;
this.threshold = threshold;
}
public static void attachToCreature(Entity creature, Entity target, double threshold){
PartyFollower ai = new PartyFollower(creature, target, threshold);
Globals.aiManager.registerAI(ai);
}
@Override
public void simulate() {
if(target == null){
Vector3d characterPosition = EntityUtils.getPosition(character);
Vector3d targetPosition = EntityUtils.getPosition(target);
if(targetPosition.distance(characterPosition) > threshold){
moveToTarget();
}
}
}
void moveToTarget(){
Vector3d targetPosition = EntityUtils.getPosition(target);
Vector3d characterPosition = EntityUtils.getPosition(character);
Vector3d moveVector = new Vector3d(targetPosition).sub(characterPosition).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);
}
}
}

View File

@ -3,6 +3,8 @@ package electrosphere.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import electrosphere.game.data.creature.type.ai.AITreeData;
import electrosphere.game.data.creature.type.ai.AITreeDataSerializer;
import electrosphere.game.data.creature.type.movement.MovementSystem;
import electrosphere.game.data.creature.type.movement.MovementSystemSerializer;
import electrosphere.logger.LoggerInterface;
@ -35,6 +37,7 @@ public class FileUtils {
//init gson
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MovementSystem.class, new MovementSystemSerializer());
gsonBuilder.registerTypeAdapter(AITreeData.class, new AITreeDataSerializer());
gsonBuilder.addDeserializationExclusionStrategy(new AnnotationExclusionStrategy());
gsonBuilder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy());
gson = gsonBuilder.create();

View File

@ -0,0 +1,10 @@
package electrosphere.util;
/**
* Utilities for random numbers
*/
public class RandomUtils {
}