Basic ai framework
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
c776dae868
commit
b10b7e8407
@ -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" : []
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -281,6 +281,7 @@ public class EntityDataStrings {
|
||||
AI stuff
|
||||
*/
|
||||
public static final String VIEW_PITCH = "aiViewPitch";
|
||||
public static final String AI = "ai";
|
||||
|
||||
/**
|
||||
* Pose actor
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
40
src/main/java/electrosphere/server/ai/AITree.java
Normal file
40
src/main/java/electrosphere/server/ai/AITree.java
Normal 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();
|
||||
|
||||
}
|
||||
344
src/main/java/electrosphere/server/ai/creature/Attacker.java
Normal file
344
src/main/java/electrosphere/server/ai/creature/Attacker.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
10
src/main/java/electrosphere/util/RandomUtils.java
Normal file
10
src/main/java/electrosphere/util/RandomUtils.java
Normal file
@ -0,0 +1,10 @@
|
||||
package electrosphere.util;
|
||||
|
||||
/**
|
||||
* Utilities for random numbers
|
||||
*/
|
||||
public class RandomUtils {
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user