package com.deftwun.zombiecopter.systems;
import net.dermetfan.gdx.physics.box2d.Box2DUtils;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.utils.ImmutableArray;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Logger;
import com.deftwun.zombiecopter.AI.BrainState;
import com.deftwun.zombiecopter.ComponentMappers;
import com.deftwun.zombiecopter.App;
import com.deftwun.zombiecopter.RayCast;
import com.deftwun.zombiecopter.components.BrainComponent;
import com.deftwun.zombiecopter.components.ControllerComponent;
import com.deftwun.zombiecopter.components.GunComponent;
import com.deftwun.zombiecopter.components.MeleeComponent;
import com.deftwun.zombiecopter.components.PhysicsComponent;
import com.deftwun.zombiecopter.components.TeamComponent;
import com.deftwun.zombiecopter.components.TeamComponent.Team;
//This AI system tries to dynamically determine how an entity should behave based on the components it contains
public class AgentSystem extends EntitySystem{
private Logger logger = new Logger("AgentSystem",Logger.INFO);
private RayCast rayCast = new RayCast();
private Family agents = Family.all(BrainComponent.class,ControllerComponent.class, PhysicsComponent.class).get();
public AgentSystem(){
logger.debug("Initializing");
}
public void update(float deltaTime){
ImmutableArray<Entity> entities = App.engine.getEntitiesFor(agents);
ComponentMappers mappers = App.engine.mappers;
for (Entity e : entities){
BrainComponent brain = mappers.brain.get(e);
brain.time += deltaTime;
if (brain.time >= brain.thinkTime){
brain.time = 0;
processEntity(e,deltaTime);
}
}
}
private void processEntity(Entity e,float deltaTime){
updateSpacialAwareness(e);
determineState(e);
handleMovement(e,deltaTime);
handleWeapons(e,deltaTime);
copterCollisionAvoidance(e,deltaTime);
walkerCliffAvoidance(e,deltaTime);
}
private void walkerCliffAvoidance(Entity e, float deltaTime){
ComponentMappers mappers = App.engine.mappers;
//Only pertains to walkers
if (!mappers.walk.has(e)) return;
ControllerComponent controller = mappers.controller.get(e);
PhysicsComponent physics = mappers.physics.get(e);
BrainComponent brain = mappers.brain.get(e);
World w = App.engine.systems.physics.world;
controller.moveVector.nor();
float x1 = brain.myPosition.x,
y1 = brain.myPosition.y,
speed = physics.getLinearVelocity().len(),
distance = physics.getLinearVelocity().scl(deltaTime).len(),
size = Box2DUtils.size(physics.getPrimaryBody()).len(),
height = speed * 5; //Dont walk off edge if nothing above this height
boolean noGroundToTheLeft = rayCast.cast(w,x1-distance,y1,x1,y1-height),
noGroundToTheRight = rayCast.cast(w,x1+distance,y1,x1,y1-height);
if (noGroundToTheLeft) controller.moveVector.add(1,0);
if (noGroundToTheRight)controller.moveVector.add(-1,0);
}
private void copterCollisionAvoidance(Entity e, float deltaTime) {
ComponentMappers mappers = App.engine.mappers;
//Only pertains to helicopters
if (!mappers.helicopter.has(e)) return;
ControllerComponent controller = mappers.controller.get(e);
PhysicsComponent physics = mappers.physics.get(e);
BrainComponent brain = mappers.brain.get(e);
World w = App.engine.systems.physics.world;
controller.moveVector.nor();
float x1 = brain.myPosition.x,
y1 = brain.myPosition.y,
s = Box2DUtils.size(physics.getPrimaryBody()).len(),
d = s + physics.getPrimaryBody().getLinearVelocity().scl(deltaTime).len() + brain.desiredRange * deltaTime;
boolean clearBelow = rayCast.cast(w,x1,y1,x1,y1-d),
clearAbove = rayCast.cast(w,x1,y1,x1,y1+d),
clearLeft = rayCast.cast(w,x1,y1,x1-d,y1),
clearRight = rayCast.cast(w,x1,y1,x1+d,y1),
clearLowLeft = rayCast.cast(w,x1,y1,x1-d*.5f,y1-d),
clearLowRight = rayCast.cast(w,x1,y1,x1+d*.5f,y1-d),
clearUpLeft = rayCast.cast(w,x1,y1,x1-d*.5f,y1+d),
clearUpRight = rayCast.cast(w,x1,y1,x1+d*.5f,y1+d);
if (clearAbove) controller.moveVector.y = 0;
if (!clearLowLeft)controller.moveVector.add(1,1);
if (!clearLowRight)controller.moveVector.add(-1,1);
if (!clearUpLeft)controller.moveVector.add(1,-1);
if (!clearUpRight)controller.moveVector.add(-1,-1);
if (!clearAbove) controller.moveVector.add(0,-1);
if (!clearBelow) controller.moveVector.add(0,1);
if (!clearLeft) controller.moveVector.add(1,0);
if (!clearRight) controller.moveVector.add(-1,0);
}
private boolean isEnemy(Team t1, Team t2){
return App.engine.systems.team.getEnemies(t1).contains(t2,false);
}
private boolean isFriend(Team t1, Team t2){
return t1 == t2;
}
//Recognize position & velocity in space & determine where things are
private void updateSpacialAwareness(Entity e){
ComponentMappers mappers = App.engine.mappers;
PhysicsComponent physics = mappers.physics.get(e);
BrainComponent brain = mappers.brain.get(e);
TeamComponent team = mappers.team.get(e);
brain.myPosition.set(physics.getPosition());
brain.myVelocity.set(physics.getLinearVelocity());
brain.closestEnemy = null;
brain.closestFriend = null;
brain.closestLeader = null;
if (team == null) return;
//Poll Physics entities in sight and Evaluate friend or foe etc...
Fixture visionSensor = physics.getFixture("visionSensor");
if (visionSensor != null){
logger.debug("Looking around");
Array<PhysicsComponent> visibleObjects =
App.engine.systems.physics.getPhysicsComponentsTouching(visionSensor);
float closestEnemyRange = -1,
closestFriendRange = -1,
closestLeaderRange = -1;
for (PhysicsComponent physicsInSight : visibleObjects){
logger.debug("Physics entity in sight");
Entity entityInSight = physicsInSight.ownerEntity;
TeamComponent teamInSight = mappers.team.get(entityInSight);
//Enemy
if (teamInSight != null && isEnemy(team.team,teamInSight.team)){
logger.debug("Entity is my ENEMY");
float distance = physicsInSight.getPosition().dst(physics.getPosition());
if (closestEnemyRange < 0) {
closestEnemyRange = distance;
brain.closestEnemy = entityInSight;
}
else if (distance < closestEnemyRange) {
brain.closestEnemy = entityInSight;
}
}
//tell brain which FRIEND is closest within sight
else if (teamInSight != null && isFriend(team.team,teamInSight.team)){
logger.debug("Entity is my FRIEND");
float distance = physicsInSight.getPosition().dst(physics.getPosition());
if (closestFriendRange < 0) {
closestFriendRange = distance;
brain.closestFriend = entityInSight;
}
else if (distance < closestFriendRange) {
brain.closestFriend = entityInSight;
}
if (mappers.leader.has(entityInSight)){
//Closest FRIEND && LEADER
logger.debug("Entity is my LEADER");
if (closestLeaderRange < 0) {
closestLeaderRange = distance;
brain.closestLeader = entityInSight;
}
else if (distance < closestFriendRange) {
brain.closestLeader = entityInSight;
}
}
}
}
}
}
private void determineState(Entity e){
BrainComponent brain = App.engine.mappers.brain.get(e);
GunComponent gun = App.engine.mappers.gun.get(e);
MeleeComponent melee = App.engine.mappers.melee.get(e);
boolean enemyInSight = brain.closestEnemy != null,
friendInSight = brain.closestFriend != null,
leaderInSight = brain.closestLeader != null;
//Follow the leader
if (!enemyInSight && leaderInSight){
brain.desiredRange = 1;
brain.state = BrainState.FOLLOW;
}
//Attack or Flee
else if (enemyInSight){
//Attack if we have a weapon
if (gun != null){
// lets try and stay within 80% of weapons range
brain.desiredRange = gun.range * .5f;
brain.state = BrainState.ATTACK;
}
else if (melee != null){
melee.target = brain.closestEnemy;
brain.desiredRange = melee.range * .5f;
brain.rangeTolerance = .1f;
brain.state = BrainState.ATTACK;
}
//Flee if were defenseless
else {
brain.state = BrainState.FLEE;
}
}
//Patrol (Look for leader / enemies)
else if (!leaderInSight){
brain.state = BrainState.PATROL;
}
logger.debug("Brain State = " + brain.state);
}
private void handleMovement(Entity e, float deltaTime){
BrainComponent brain = App.engine.mappers.brain.get(e);
ControllerComponent controller = App.engine.mappers.controller.get(e);
switch (brain.state){
//PATROL only controls movement along the x axis. Helicopters might not do so well unless I figure out a way to
// maintain desired altitude
case PATROL: {
controller.moveVector.y=0;
//TODO: This should work off patrol distance or something
//50% chance of changing direction
if (MathUtils.randomBoolean(.5f)){
logger.debug("Patrol: Changing direction");
//33% percent chance to walk left,right or stop
if (MathUtils.randomBoolean(.44f)){
controller.moveVector.x = -1;
logger.debug("Patrol: Move Left");
}
else if (MathUtils.randomBoolean(.88f)){
controller.moveVector.x = 1;
logger.debug("Patrol: Move Right");
}
else {
controller.moveVector.x = 0;
logger.debug("Patrol: Stay Put");
}
}
//Look where your going
controller.lookVector.set(controller.moveVector);
break;
}
case ATTACK:{
PhysicsComponent enemyPhysics = App.engine.mappers.physics.get(brain.closestEnemy);
if (enemyPhysics == null) break;
//Look at enemy
controller.lookVector.set(enemyPhysics.getPosition()).sub(brain.myPosition).nor();
//Don't move if were within desiredRange +- rangeTolerance
float distance = brain.myPosition.dst(enemyPhysics.getPosition());
if (distance < (brain.desiredRange + brain.rangeTolerance) &&
distance > (brain.desiredRange - brain.rangeTolerance))
controller.moveVector.set(0,0);
//Move towards enemy
else if (distance > brain.desiredRange){
controller.moveVector.set(enemyPhysics.getPosition()).sub(brain.myPosition).nor();
}
//Move away from enemy
else if (distance < brain.desiredRange){
controller.moveVector.set(brain.myPosition).sub(enemyPhysics.getPosition()).nor();
}
break;
}
case FLEE:{
PhysicsComponent enemyPhysics = App.engine.mappers.physics.get(brain.closestEnemy);
if (enemyPhysics == null) break;
//Run away
controller.moveVector.set(brain.myPosition).sub(enemyPhysics.getPosition()).nor();
//Look where your going
controller.lookVector.set(controller.moveVector);
break;
}
case FOLLOW:{
PhysicsComponent leaderPhysics = App.engine.mappers.physics.get(brain.closestLeader);
if (leaderPhysics == null) break;
//Don't move if we're within desiredRange +- rangeTolerance
float distance = brain.myPosition.dst(leaderPhysics.getPosition());
if (distance < (brain.desiredRange + brain.rangeTolerance) &&
distance > (brain.desiredRange - brain.rangeTolerance))
controller.moveVector.set(0,0);
//Move towards leader
else if (distance > brain.desiredRange / 2){
controller.moveVector.set(leaderPhysics.getPosition()).sub(brain.myPosition).nor();
controller.lookVector.set(controller.moveVector);
controller.moveVector.y = 0;
}
break;
}
default: brain.state = BrainState.PATROL;
}
}
private void handleWeapons(Entity e, float deltaTime){
ComponentMappers mappers = App.engine.mappers;
if (!mappers.gun.has(e) && !mappers.melee.has(e)) return;
logger.debug("Handle weapons");
BrainComponent brain = mappers.brain.get(e);
ControllerComponent controller = mappers.controller.get(e);
PhysicsComponent physics = mappers.physics.get(e);
switch (brain.state){
case ATTACK:{
World w = App.engine.systems.physics.world;
PhysicsComponent enemyPhysics = mappers.physics.get(brain.closestEnemy);
boolean clearShot = rayCast.cast(w,physics,enemyPhysics),
inRange = brain.myPosition.dst(enemyPhysics.getPosition()) <= brain.desiredRange;
logger.debug("Attacking? "+(inRange && clearShot) + " because ; inRange=" + inRange + " ; clearShot=" + clearShot);
controller.attack = inRange && clearShot;
break;
}
default:
controller.attack = false;
break;
}
}
}