-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add Ai-System with FightAI and IdleAi and some walk implementations * rename interfaces * stop using hero.getPositionComponent * set Velcoity to 0,0 after movement * add doc to AITools * javadoc and some code opt * rebase * add dsl annoations * remove test monster * add aicomponent to dsl input * Workaround for AIComponent type * Decrease monster velocity * spotless apply Co-authored-by: mr <[email protected]>
- Loading branch information
Showing
16 changed files
with
384 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package ecs.components.ai; | ||
|
||
import ecs.components.Component; | ||
import ecs.components.ai.fight.IFightAI; | ||
import ecs.components.ai.idle.IIdleAI; | ||
import ecs.components.ai.idle.RadiusWalk; | ||
import ecs.components.ai.transition.ITransition; | ||
import ecs.components.ai.transition.RangeTransition; | ||
import ecs.entities.Entity; | ||
import semanticAnalysis.types.DSLContextMember; | ||
import semanticAnalysis.types.DSLType; | ||
|
||
/** AIComponent is a component that stores the idle and combat behavior of AI controlled entities */ | ||
@DSLType(name = "ai_component") | ||
public class AIComponent extends Component { | ||
|
||
public static String name = "AIComponent"; | ||
private /*@DSLTypeMember*/ IFightAI fightAI; | ||
private /*@DSLTypeMember*/ IIdleAI idleAI; | ||
private /*@DSLTypeMember*/ ITransition transition; | ||
|
||
/** | ||
* @param entity associated entity | ||
* @param fightAI combat behavior | ||
* @param idleAI idle behavior | ||
* @param transition Determines when to fight | ||
*/ | ||
public AIComponent(Entity entity, IFightAI fightAI, IIdleAI idleAI, ITransition transition) { | ||
super(entity, name); | ||
this.fightAI = fightAI; | ||
this.idleAI = idleAI; | ||
this.transition = transition; | ||
} | ||
|
||
/** | ||
* @param entity associated entity | ||
*/ | ||
public AIComponent(@DSLContextMember(name = "entity") Entity entity) { | ||
super(entity, name); | ||
System.out.println("DEBUG AI"); | ||
idleAI = new RadiusWalk(5); | ||
transition = new RangeTransition(1.5f); | ||
fightAI = | ||
entity1 -> { | ||
System.out.println("TIME TO FIGHT!"); | ||
// todo replace with melee skill | ||
}; | ||
} | ||
|
||
/** Excecute the ai behavior */ | ||
public void execute() { | ||
if (transition.isInFightMode(entity)) fightAI.fight(entity); | ||
else idleAI.idle(entity); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package ecs.components.ai; | ||
|
||
import com.badlogic.gdx.ai.pfa.GraphPath; | ||
import ecs.components.PositionComponent; | ||
import ecs.components.VelocityComponent; | ||
import ecs.entities.Entity; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Random; | ||
import level.elements.ILevel; | ||
import level.elements.tile.Tile; | ||
import level.tools.Coordinate; | ||
import mydungeon.ECS; | ||
import tools.Point; | ||
|
||
public class AITools { | ||
private static final Random random = new Random(); | ||
|
||
/** | ||
* Finds the path to a random (accessible) tile in the given radius, starting from the position | ||
* of the given entity. | ||
* | ||
* @param entity Entity whose position is the center point | ||
* @param radius Search radius | ||
* @return Path from the position of the entity to the randomly selected tile | ||
*/ | ||
public static GraphPath<Tile> calculateNewPath(Entity entity, float radius) { | ||
PositionComponent pc = (PositionComponent) entity.getComponent(PositionComponent.name); | ||
VelocityComponent vc = (VelocityComponent) entity.getComponent(VelocityComponent.name); | ||
if (pc != null && vc != null) { | ||
ILevel level = ECS.currentLevel; | ||
Point position = pc.getPosition(); | ||
List<Tile> tiles = new ArrayList<>(); | ||
for (float x = position.x - radius; x <= position.x + radius; x++) { | ||
for (float y = position.y - radius; y <= position.y + radius; y++) { | ||
tiles.add(level.getTileAt(new Point(x, y).toCoordinate())); | ||
} | ||
} | ||
tiles.removeIf(Objects::isNull); | ||
tiles.removeIf(tile -> !tile.isAccessible()); | ||
Coordinate newPosition = tiles.get(random.nextInt(tiles.size())).getCoordinate(); | ||
return level.findPath( | ||
level.getTileAt(position.toCoordinate()), level.getTileAt(newPosition)); | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Finds the path from the position of one entity to the position of another entity. | ||
* | ||
* @param from Entity whose position is the start point | ||
* @param to Entity whose position is the goal point | ||
* @return Path | ||
*/ | ||
public static GraphPath<Tile> calculateNewPath(Entity from, Entity to) { | ||
PositionComponent myPositionComponent = | ||
(PositionComponent) from.getComponent(PositionComponent.name); | ||
PositionComponent heroPositionComponent = | ||
(PositionComponent) to.getComponent(PositionComponent.name); | ||
if (myPositionComponent != null && heroPositionComponent != null) { | ||
ILevel level = ECS.currentLevel; | ||
Coordinate myPosition = myPositionComponent.getPosition().toCoordinate(); | ||
Coordinate heroposition = heroPositionComponent.getPosition().toCoordinate(); | ||
return level.findPath(level.getTileAt(myPosition), level.getTileAt(heroposition)); | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Sets the velocity of the passed entity so that it takes the next necessary step to get to the | ||
* end of the path. | ||
* | ||
* @param entity Entity moving on the path | ||
* @param path Path on which the entity moves | ||
*/ | ||
public static void move(Entity entity, GraphPath<Tile> path) { | ||
PositionComponent pc = (PositionComponent) entity.getComponent(PositionComponent.name); | ||
VelocityComponent vc = (VelocityComponent) entity.getComponent(VelocityComponent.name); | ||
ILevel level = ECS.currentLevel; | ||
Tile currentTile = level.getTileAt(pc.getPosition().toCoordinate()); | ||
int i = 0; | ||
Tile nextTile = null; | ||
do { | ||
if (i >= path.getCount()) return; | ||
if (path.get(i).equals(currentTile)) { | ||
nextTile = path.get(i + 1); | ||
} | ||
i++; | ||
} while (nextTile == null); | ||
|
||
switch (currentTile.directionTo(nextTile)[0]) { | ||
case N -> vc.setY(vc.getySpeed()); | ||
case S -> vc.setY(-vc.getySpeed()); | ||
case E -> vc.setX(vc.getxSpeed()); | ||
case W -> vc.setX(-vc.getxSpeed()); | ||
} | ||
if (currentTile.directionTo(nextTile).length > 1) | ||
switch (currentTile.directionTo(nextTile)[1]) { | ||
case N -> vc.setY(vc.getySpeed()); | ||
case S -> vc.setY(-vc.getySpeed()); | ||
case E -> vc.setX(vc.getxSpeed()); | ||
case W -> vc.setX(-vc.getxSpeed()); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the position of the player is within the given radius of the position of the given | ||
* entity. | ||
* | ||
* @param entity Entity whose position specifies the center point | ||
* @param range Reichweite die betrachtet werden soll | ||
* @return Ob sich der Spieler in Reichweite befindet | ||
*/ | ||
public static boolean playerInRange(Entity entity, float range) { | ||
PositionComponent myPositionComponent = | ||
(PositionComponent) entity.getComponent(PositionComponent.name); | ||
if (ECS.hero != null) { | ||
PositionComponent heroPositionComponent = | ||
(PositionComponent) ECS.hero.getComponent(PositionComponent.name); | ||
if (heroPositionComponent != null) { | ||
Point myPosition = myPositionComponent.getPosition(); | ||
Point heroPosition = heroPositionComponent.getPosition(); | ||
|
||
float xDiff = myPosition.x - heroPosition.x; | ||
float yDiff = myPosition.y - heroPosition.y; | ||
float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff); | ||
return distance <= range; | ||
} | ||
} | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package ecs.components.ai.fight; | ||
|
||
import ecs.entities.Entity; | ||
|
||
public interface IFightAI { | ||
|
||
/** | ||
* Implements the combat behavior of an AI controlled entity | ||
* | ||
* @param entity associated entity | ||
*/ | ||
void fight(Entity entity); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package ecs.components.ai.fight; | ||
|
||
import com.badlogic.gdx.ai.pfa.GraphPath; | ||
import ecs.components.ai.AITools; | ||
import ecs.components.skill.Skill; | ||
import ecs.entities.Entity; | ||
import java.lang.reflect.InvocationTargetException; | ||
import level.elements.tile.Tile; | ||
import mydungeon.ECS; | ||
import tools.Constants; | ||
|
||
public class MeleeAI implements IFightAI { | ||
private final float attackRange; | ||
private final int delay = Constants.FRAME_RATE; | ||
private int timeSinceLastUpdate = 0; | ||
private final Skill fightSkill; | ||
private GraphPath<Tile> path; | ||
|
||
/** | ||
* Attacks the player if he is within the given range. Otherwise, it will move towards the | ||
* player. | ||
* | ||
* @param attackRange Range in which the attack skill should be executed | ||
* @param fightSkill Skill to be used when an attack is performed | ||
*/ | ||
public MeleeAI(float attackRange, Skill fightSkill) { | ||
this.attackRange = attackRange; | ||
this.fightSkill = fightSkill; | ||
} | ||
|
||
@Override | ||
public void fight(Entity entity) { | ||
if (AITools.playerInRange(entity, attackRange)) { | ||
try { | ||
fightSkill.execute(entity); | ||
} catch (InvocationTargetException | IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} else { | ||
if (timeSinceLastUpdate >= delay) { | ||
path = AITools.calculateNewPath(entity, ECS.hero); | ||
timeSinceLastUpdate = -1; | ||
} | ||
timeSinceLastUpdate++; | ||
AITools.move(entity, path); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package ecs.components.ai.idle; | ||
|
||
import ecs.entities.Entity; | ||
|
||
public interface IIdleAI { | ||
|
||
/** | ||
* Implements the idle behavior of an AI controlled entity | ||
* | ||
* @param entity associated entity | ||
*/ | ||
void idle(Entity entity); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package ecs.components.ai.idle; | ||
|
||
import com.badlogic.gdx.ai.pfa.GraphPath; | ||
import ecs.components.PositionComponent; | ||
import ecs.components.ai.AITools; | ||
import ecs.entities.Entity; | ||
import level.elements.ILevel; | ||
import level.elements.tile.Tile; | ||
import mydungeon.ECS; | ||
import tools.Constants; | ||
|
||
public class RadiusWalk implements IIdleAI { | ||
private final float radius; | ||
private GraphPath<Tile> path; | ||
private final int breakTime = Constants.FRAME_RATE * 5; | ||
private int currentBreak = 0; | ||
|
||
/** | ||
* Finds a point in the radius and then moves there. When the point has been reached, a new | ||
* point in the radius is searched for from there. | ||
* | ||
* @param radius Radius in which a target point is to be searched for | ||
*/ | ||
public RadiusWalk(float radius) { | ||
this.radius = radius; | ||
} | ||
|
||
@Override | ||
public void idle(Entity entity) { | ||
if (path == null || pathFinished(entity)) { | ||
if (currentBreak >= breakTime) { | ||
currentBreak = 0; | ||
path = AITools.calculateNewPath(entity, radius); | ||
idle(entity); | ||
} | ||
|
||
currentBreak++; | ||
|
||
} else AITools.move(entity, path); | ||
} | ||
|
||
private boolean pathFinished(Entity entity) { | ||
PositionComponent pc = (PositionComponent) entity.getComponent(PositionComponent.name); | ||
ILevel level = ECS.currentLevel; | ||
return path.get(path.getCount() - 1) | ||
.equals(level.getTileAt(pc.getPosition().toCoordinate())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package ecs.components.ai.transition; | ||
|
||
import ecs.entities.Entity; | ||
|
||
/** Determines when an ai switches between idle and fight */ | ||
public interface ITransition { | ||
|
||
/** | ||
* Function that determines whether an entity should be in combat mode | ||
* | ||
* @param entity associated entity | ||
* @return if the entity should fight | ||
*/ | ||
boolean isInFightMode(Entity entity); | ||
} |
23 changes: 23 additions & 0 deletions
23
game/src/ecs/components/ai/transition/RangeTransition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package ecs.components.ai.transition; | ||
|
||
import ecs.components.ai.AITools; | ||
import ecs.entities.Entity; | ||
|
||
public class RangeTransition implements ITransition { | ||
|
||
private final float range; | ||
|
||
/** | ||
* Switches to combat mode when the player is within range of the entity. | ||
* | ||
* @param range Range of the entity. | ||
*/ | ||
public RangeTransition(float range) { | ||
this.range = range; | ||
} | ||
|
||
@Override | ||
public boolean isInFightMode(Entity entity) { | ||
return AITools.playerInRange(entity, range); | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
game/src/ecs/components/Skill.java → game/src/ecs/components/skill/Skill.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
game/src/ecs/components/SkillComponent.java → .../ecs/components/skill/SkillComponent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.