Multi-target AI

This commit is contained in:
Anuken
2020-08-03 22:53:35 -04:00
parent b1da2faa04
commit 2466267b1c
9 changed files with 131 additions and 83 deletions
+17 -26
View File
@@ -1,54 +1,45 @@
package mindustry.ai.types; package mindustry.ai.types;
import arc.math.*; import arc.math.*;
import arc.math.geom.*;
import arc.util.*; import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.units.*; import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
public class FlyingAI extends AIController{ public class FlyingAI extends AIController{
@Override @Override
public void updateUnit(){ public void updateMovement(){
if(unit.moving()){ if(unit.moving()){
unit.rotation(unit.vel().angle()); unit.lookAt(unit.vel.angle());
} }
if(unit.isFlying()){ if(unit.isFlying()){
unit.wobble(); unit.wobble();
} }
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
target = null;
}
if(retarget()){
targetClosest();
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
}
boolean shoot = false;
if(target != null && unit.hasWeapons()){ if(target != null && unit.hasWeapons()){
if(unit.type().weapons.first().rotate){ if(unit.type().weapons.first().rotate){
moveTo(unit.range() * 0.85f); moveTo(unit.range() * 0.8f);
unit.lookAt(target); unit.lookAt(target);
}else{ }else{
attack(80f); attack(80f);
} }
shoot = unit.inRange(target);
if(shoot && unit.type().hasWeapons()){
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
unit.aim(to);
}
} }
}
unit.controlWeapons(shoot, shoot); @Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
Teamc result = target(x, y, range, air, ground);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
if(result != null) return result;
return null;
} }
//TODO clean up //TODO clean up
+1 -9
View File
@@ -12,15 +12,7 @@ import static mindustry.Vars.pathfinder;
public class GroundAI extends AIController{ public class GroundAI extends AIController{
@Override @Override
public void updateUnit(){ public void updateMovement(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
}
if(retarget()){
targetClosest();
}
Building core = unit.closestEnemyCore(); Building core = unit.closestEnemyCore();
+1 -1
View File
@@ -17,7 +17,7 @@ public class SuicideAI extends GroundAI{
} }
if(retarget()){ if(retarget()){
targetClosest(); target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
} }
Building core = unit.closestEnemyCore(); Building core = unit.closestEnemyCore();
+15 -24
View File
@@ -430,34 +430,25 @@ public class Bullets implements ContentList{
} }
}; };
basicFlame = new BulletType(3f, 15f){ basicFlame = new BulletType(3.35f, 15f){{
{ ammoMultiplier = 3f;
ammoMultiplier = 3f; hitSize = 7f;
hitSize = 7f; lifetime = 18f;
lifetime = 42f; pierce = true;
pierce = true; statusDuration = 60f * 4;
drag = 0.05f; shootEffect = Fx.shootSmallFlame;
statusDuration = 60f * 4; hitEffect = Fx.hitFlameSmall;
shootEffect = Fx.shootSmallFlame; despawnEffect = Fx.none;
hitEffect = Fx.hitFlameSmall; status = StatusEffects.burning;
despawnEffect = Fx.none; keepVelocity = false;
status = StatusEffects.burning; hittable = false;
keepVelocity = false; }};
hittable = false;
}
@Override pyraFlame = new BulletType(3.35f, 22f){{
public float range(){
return 50f;
}
};
pyraFlame = new BulletType(3.3f, 22f){{
ammoMultiplier = 4f; ammoMultiplier = 4f;
hitSize = 7f; hitSize = 7f;
lifetime = 42f; lifetime = 18f;
pierce = true; pierce = true;
drag = 0.05f;
statusDuration = 60f * 6; statusDuration = 60f * 6;
shootEffect = Fx.shootPyraFlame; shootEffect = Fx.shootPyraFlame;
hitEffect = Fx.hitFlameSmall; hitEffect = Fx.hitFlameSmall;
+1 -1
View File
@@ -668,7 +668,7 @@ public class UnitTypes implements ContentList{
collidesTiles = false; collidesTiles = false;
ammoMultiplier = 4f; ammoMultiplier = 4f;
splashDamageRadius = 60f; splashDamageRadius = 60f;
splashDamage = 80f; splashDamage = 60f;
backColor = Pal.missileYellowBack; backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow; frontColor = Pal.missileYellow;
trailEffect = Fx.artilleryTrail; trailEffect = Fx.artilleryTrail;
@@ -50,10 +50,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
lookAt(x, y); lookAt(x, y);
} }
public boolean inRange(Position other){
return within(other, type.range);
}
public boolean hasWeapons(){ public boolean hasWeapons(){
return type.hasWeapons(); return type.hasWeapons();
} }
public float range(){
return type.range;
}
@Replace @Replace
public float clipSize(){ public float clipSize(){
return type.region.getWidth() * 2f; return type.region.getWidth() * 2f;
@@ -24,7 +24,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
/** weapon mount array, never null */ /** weapon mount array, never null */
@SyncLocal WeaponMount[] mounts = {}; @SyncLocal WeaponMount[] mounts = {};
@ReadOnly transient float range, aimX, aimY; @ReadOnly transient float aimX, aimY;
@ReadOnly transient boolean isRotate; @ReadOnly transient boolean isRotate;
boolean isShooting; boolean isShooting;
float ammo; float ammo;
@@ -35,16 +35,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
} }
} }
boolean inRange(Position other){
return within(other, range);
}
void setupWeapons(UnitType def){ void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size]; mounts = new WeaponMount[def.weapons.size];
range = def.range;
for(int i = 0; i < mounts.length; i++){ for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i)); mounts[i] = new WeaponMount(def.weapons.get(i));
range = Math.max(range, def.weapons.get(i).bullet.range());
} }
} }
@@ -103,7 +97,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation; mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta); mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else{ }else if(!weapon.rotate){
mount.rotation = 0; mount.rotation = 0;
mount.targetRotation = angleTo(mount.aimX, mount.aimY); mount.targetRotation = angleTo(mount.aimX, mount.aimY);
} }
@@ -5,42 +5,110 @@ import arc.math.geom.*;
import arc.util.*; import arc.util.*;
import mindustry.entities.*; import mindustry.entities.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*; import mindustry.world.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
import static mindustry.Vars.indexer; import static mindustry.Vars.*;
public class AIController implements UnitController{ public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2(); protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0; protected static final int timerTarget = 0;
protected Unit unit; protected Unit unit;
protected Teamc target;
protected Interval timer = new Interval(4); protected Interval timer = new Interval(4);
/** main target that is being faced */
protected Teamc target;
/** targets for each weapon */
protected Teamc[] targets = {};
{ {
timer.reset(0, Mathf.random(40f)); timer.reset(0, Mathf.random(40f));
} }
protected void targetClosestAllyFlag(BlockFlag flag){ @Override
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team, flag)); public void updateUnit(){
if(target != null) this.target = target.build; updateTargeting();
updateMovement();
} }
protected void targetClosestEnemyFlag(BlockFlag flag){ protected void updateMovement(){
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team, flag));
if(target != null) this.target = target.build; }
protected void updateTargeting(){
if(unit.hasWeapons()){
updateWeapons();
}
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
float rotation = unit.rotation - 90;
boolean ret = retarget();
if(ret){
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
}
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y)){
target = null;
}
for(int i = 0; i < targets.length; i++){
WeaponMount mount = unit.mounts[i];
Weapon weapon = mount.weapon;
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
if(unit.type().singleTarget){
targets[i] = target;
}else{
if(ret){
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
}
if(Units.invalidateTarget(targets[i], unit.team, mountX, mountY, weapon.bullet.range())){
targets[i] = null;
}
}
boolean shoot = false;
if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range());
if(shoot){
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
}
mount.shoot = shoot;
mount.rotate = shoot;
}
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build;
}
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
} }
protected boolean retarget(){ protected boolean retarget(){
return timer.get(timerTarget, 30); return timer.get(timerTarget, 30);
} }
protected void targetClosest(){ protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
Teamc newTarget = Units.closestTarget(unit.team, unit.x, unit.y, Math.max(unit.range(), unit.type().range), u -> (unit.type().targetAir && u.isFlying()) || (unit.type().targetGround && !u.isFlying())); return target(x, y, range, air, ground);
if(newTarget != null){
target = newTarget;
}
} }
protected void init(){ protected void init(){
+5 -1
View File
@@ -80,6 +80,7 @@ public class UnitType extends UnlockableContent{
public float trailX = 4f, trailY = -3f, trailScl = 1f; public float trailX = 4f, trailY = -3f, trailScl = 1f;
/** Whether the unit can heal blocks. Initialized in init() */ /** Whether the unit can heal blocks. Initialized in init() */
public boolean canHeal = false; public boolean canHeal = false;
public boolean singleTarget = false;
public ObjectSet<StatusEffect> immunities = new ObjectSet<>(); public ObjectSet<StatusEffect> immunities = new ObjectSet<>();
public Sound deathSound = Sounds.bang; public Sound deathSound = Sounds.bang;
@@ -180,10 +181,13 @@ public class UnitType extends UnlockableContent{
public void init(){ public void init(){
if(constructor == null) throw new IllegalArgumentException("no constructor set up for unit '" + name + "'"); if(constructor == null) throw new IllegalArgumentException("no constructor set up for unit '" + name + "'");
singleTarget = weapons.size <= 1;
//set up default range //set up default range
if(range < 0){ if(range < 0){
range = Float.MAX_VALUE;
for(Weapon weapon : weapons){ for(Weapon weapon : weapons){
range = Math.max(range, weapon.bullet.range()); range = Math.min(range, weapon.bullet.range() + hitsize/2f);
} }
} }