From b2fe11934821a2c4e88880abc5c1ca382ae36399 Mon Sep 17 00:00:00 2001 From: austin Date: Sat, 20 Nov 2021 12:55:13 -0500 Subject: [PATCH] Opportunistic Attacker --- .../electrosphere/engine/LoadingThread.java | 4 +- .../entity/EntityDataStrings.java | 1 + .../entity/types/item/ItemUtils.java | 7 + .../ai/creature/OpportunisticAttacker.java | 264 ++++++++++++++++++ 4 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 src/main/java/electrosphere/game/server/ai/creature/OpportunisticAttacker.java diff --git a/src/main/java/electrosphere/engine/LoadingThread.java b/src/main/java/electrosphere/engine/LoadingThread.java index 5e1dab15..43c9ae56 100644 --- a/src/main/java/electrosphere/engine/LoadingThread.java +++ b/src/main/java/electrosphere/engine/LoadingThread.java @@ -43,6 +43,7 @@ import electrosphere.renderer.Model; import electrosphere.renderer.RenderUtils; import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.game.client.targeting.crosshair.Crosshair; +import electrosphere.game.server.ai.creature.OpportunisticAttacker; import electrosphere.game.server.pathfinding.NavMeshPathfinder; import electrosphere.game.server.pathfinding.navmesh.NavCube; import electrosphere.game.server.pathfinding.navmesh.NavMesh; @@ -621,7 +622,8 @@ public class LoadingThread extends Thread { // Entity goblinSword = ItemUtils.spawnBasicItem("Katana"); // AttachUtils.attachEntityToEntityAtBone(goblin, goblinSword, "Bone.031"); //attach ai to evil goblin - MindlessAttacker.attachToCreature(goblin); +// MindlessAttacker.attachToCreature(goblin); + OpportunisticAttacker.attachToCreature(goblin); // goblin = CreatureUtils.spawnBasicCreature("Goblin"); // CollisionObjUtils.positionCharacter(goblin, new Vector3f(3, 0, 4)); diff --git a/src/main/java/electrosphere/entity/EntityDataStrings.java b/src/main/java/electrosphere/entity/EntityDataStrings.java index 74fb960f..97b8b89d 100644 --- a/src/main/java/electrosphere/entity/EntityDataStrings.java +++ b/src/main/java/electrosphere/entity/EntityDataStrings.java @@ -134,6 +134,7 @@ public class EntityDataStrings { */ public static final String ITEM_IS_ITEM = "itemIsItem"; public static final String ITEM_TYPE = "itemType"; + public static final String ITEM_IS_WEAPON = "itemIsWeapon"; /* diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index aacc66f2..e3e3d364 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -89,6 +89,9 @@ public class ItemUtils { case "TARGETABLE": Globals.entityManager.registerTargetableEntity(rVal); break; + case "WEAPON": + rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true); + break; } } rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true); @@ -160,4 +163,8 @@ public class ItemUtils { public static String getType(Entity item){ return (String)item.getData(EntityDataStrings.ITEM_TYPE); } + + public static boolean isWeapon(Entity item){ + return item.getDataKeys().contains(EntityDataStrings.ITEM_IS_WEAPON); + } } diff --git a/src/main/java/electrosphere/game/server/ai/creature/OpportunisticAttacker.java b/src/main/java/electrosphere/game/server/ai/creature/OpportunisticAttacker.java new file mode 100644 index 00000000..f7e8d8fc --- /dev/null +++ b/src/main/java/electrosphere/game/server/ai/creature/OpportunisticAttacker.java @@ -0,0 +1,264 @@ +package electrosphere.game.server.ai.creature; + +import electrosphere.entity.Entity; +import electrosphere.entity.EntityDataStrings; +import electrosphere.entity.EntityUtils; +import electrosphere.entity.state.AttackTree; +import electrosphere.entity.state.equip.EquipState; +import electrosphere.entity.state.movement.GroundMovementTree; +import electrosphere.entity.types.creature.CreatureUtils; +import electrosphere.entity.types.item.ItemUtils; +import electrosphere.game.server.ai.AI; +import electrosphere.main.Globals; +import org.joml.Vector3d; + +/** + * + * @author amaterasu + */ +public class OpportunisticAttacker extends AI { + + + Entity character; + + Entity target; + + float aggroRange = 10.0f; + float attackRange = 1.0f; + + float weaponSeekRange = 10.0f; + float pickupRange = 2.0f; + + int attackCooldownMax = 250; + int attackCooldown = 0; + + enum OpportunisticAttackerGoal { + ATTACK, + SEEK_WEAPON, + FLEE, + IDLE, + } + + OpportunisticAttackerGoal goal; + + OpportunisticAttacker(Entity character){ + this.character = character; + } + + public static void attachToCreature(Entity creature){ + OpportunisticAttacker ai = new OpportunisticAttacker(creature); + ai.goal = OpportunisticAttackerGoal.IDLE; + Globals.aiManager.registerAI(ai); + } + + + + @Override + public void simulate(){ + switch(goal){ + case ATTACK: + if(target != null){ + if(inAttackRange()){ + if(hasWeapon()){ + attack(); + } else { + if(weaponInRange()){ + setGoal(OpportunisticAttackerGoal.SEEK_WEAPON); + } else { + setGoal(OpportunisticAttackerGoal.IDLE); + } + } + } else { + if(inAggroRange()){ + if(hasWeapon()){ + moveToTarget(); + } else { + if(weaponInRange()){ + setGoal(OpportunisticAttackerGoal.SEEK_WEAPON); + } else { + setGoal(OpportunisticAttackerGoal.FLEE); + } + } + } else { + target = null; + setGoal(OpportunisticAttackerGoal.IDLE); + } + } + } else { + setGoal(OpportunisticAttackerGoal.IDLE); + } + break; + case SEEK_WEAPON: + if(target != null){ + if(weaponInRange()){ + if(weaponInPickupRange()){ + pickupWeapon(); + searchForTarget(); + if(target != null){ + setGoal(OpportunisticAttackerGoal.ATTACK); + } + } else { + moveToTarget(); + } + } else { + setGoal(OpportunisticAttackerGoal.FLEE); + } + } else { + if(inAggroRange()){ + if(hasWeapon()){ + moveToTarget(); + } else { + if(!weaponInRange()){ + setGoal(OpportunisticAttackerGoal.FLEE); + } + } + } else { + target = null; + setGoal(OpportunisticAttackerGoal.IDLE); + } + } + break; + case FLEE: + if(target != null){ + if(weaponInRange()){ + setGoal(OpportunisticAttackerGoal.SEEK_WEAPON); + } else { + if(inAggroRange()){ + //find direction opposite target and move there + } else { + target = null; + setGoal(OpportunisticAttackerGoal.IDLE); + } + } + } else { + setGoal(OpportunisticAttackerGoal.IDLE); + } + break; + case IDLE: + searchForTarget(); + break; + } + + } + + void attack(){ + if(attackCooldown == 0){ + attackCooldown = attackCooldownMax; + 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 Vector3d((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; + setGoal(OpportunisticAttackerGoal.ATTACK); + break; + } + } + } + } + + boolean hasWeapon(){ + boolean rVal = false; + if(character.getDataKeys().contains(EntityDataStrings.EQUIP_STATE)){ + EquipState equipState = (EquipState)character.getData(EntityDataStrings.EQUIP_STATE); + if(equipState.hasEquipPrimary()){ + rVal = true; + } + } + return rVal; + } + + boolean weaponInRange(){ + boolean rVal = false; + Vector3d position = EntityUtils.getPosition(character); + for(Entity current : Globals.entityManager.getItemEntities()){ + if(current != character && ItemUtils.isItem(current) && ItemUtils.isWeapon(current)){ + Vector3d potentialTargetPosition = EntityUtils.getPosition(current); + if(position.distance(potentialTargetPosition) < weaponSeekRange){ + target = current; + return true; + } + } + } + return rVal; + } + + boolean weaponInPickupRange(){ + Vector3d position = EntityUtils.getPosition(character); + if(target != null){ + Vector3d potentialTargetPosition = EntityUtils.getPosition(target); + if(position.distance(potentialTargetPosition) < pickupRange){ + return true; + } + } + return false; + } + + void pickupWeapon(){ + if(character.getDataKeys().contains(EntityDataStrings.EQUIP_STATE)){ + EquipState equipState = (EquipState)character.getData(EntityDataStrings.EQUIP_STATE); + if(!equipState.hasEquipPrimary()){ + equipState.attemptEquip(target); + } + } + } + + void setGoal(OpportunisticAttackerGoal goal){ + this.goal = goal; + switch(goal){ + case IDLE: +// System.out.println("Set goal idle"); + break; + case FLEE: +// System.out.println("Set goal flee"); + break; + case SEEK_WEAPON: +// System.out.println("Set goal seek weapon"); + break; + case ATTACK: +// System.out.println("Set goal attack"); + break; + } + } + +}