Multi-target AI
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(){
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user