From 2466267b1c898c286c4fc2992124733e78d07688 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 3 Aug 2020 22:53:35 -0400 Subject: [PATCH] Multi-target AI --- core/src/mindustry/ai/types/FlyingAI.java | 43 ++++----- core/src/mindustry/ai/types/GroundAI.java | 10 +- core/src/mindustry/ai/types/SuicideAI.java | 2 +- core/src/mindustry/content/Bullets.java | 39 +++----- core/src/mindustry/content/UnitTypes.java | 2 +- .../src/mindustry/entities/comp/UnitComp.java | 8 ++ .../mindustry/entities/comp/WeaponsComp.java | 10 +- .../entities/units/AIController.java | 94 ++++++++++++++++--- core/src/mindustry/type/UnitType.java | 6 +- 9 files changed, 131 insertions(+), 83 deletions(-) diff --git a/core/src/mindustry/ai/types/FlyingAI.java b/core/src/mindustry/ai/types/FlyingAI.java index 80d712573a..2a09c1403c 100644 --- a/core/src/mindustry/ai/types/FlyingAI.java +++ b/core/src/mindustry/ai/types/FlyingAI.java @@ -1,54 +1,45 @@ package mindustry.ai.types; import arc.math.*; -import arc.math.geom.*; import arc.util.*; -import mindustry.entities.*; import mindustry.entities.units.*; +import mindustry.gen.*; import mindustry.world.meta.*; public class FlyingAI extends AIController{ @Override - public void updateUnit(){ + public void updateMovement(){ if(unit.moving()){ - unit.rotation(unit.vel().angle()); + unit.lookAt(unit.vel.angle()); } if(unit.isFlying()){ 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(unit.type().weapons.first().rotate){ - moveTo(unit.range() * 0.85f); + moveTo(unit.range() * 0.8f); unit.lookAt(target); }else{ 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 diff --git a/core/src/mindustry/ai/types/GroundAI.java b/core/src/mindustry/ai/types/GroundAI.java index 0a7384ff45..4378133ef3 100644 --- a/core/src/mindustry/ai/types/GroundAI.java +++ b/core/src/mindustry/ai/types/GroundAI.java @@ -12,15 +12,7 @@ import static mindustry.Vars.pathfinder; public class GroundAI extends AIController{ @Override - public void updateUnit(){ - - if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){ - target = null; - } - - if(retarget()){ - targetClosest(); - } + public void updateMovement(){ Building core = unit.closestEnemyCore(); diff --git a/core/src/mindustry/ai/types/SuicideAI.java b/core/src/mindustry/ai/types/SuicideAI.java index 57638d7597..354b90f8b3 100644 --- a/core/src/mindustry/ai/types/SuicideAI.java +++ b/core/src/mindustry/ai/types/SuicideAI.java @@ -17,7 +17,7 @@ public class SuicideAI extends GroundAI{ } if(retarget()){ - targetClosest(); + target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround); } Building core = unit.closestEnemyCore(); diff --git a/core/src/mindustry/content/Bullets.java b/core/src/mindustry/content/Bullets.java index 66437f3264..a5ece85275 100644 --- a/core/src/mindustry/content/Bullets.java +++ b/core/src/mindustry/content/Bullets.java @@ -430,34 +430,25 @@ public class Bullets implements ContentList{ } }; - basicFlame = new BulletType(3f, 15f){ - { - ammoMultiplier = 3f; - hitSize = 7f; - lifetime = 42f; - pierce = true; - drag = 0.05f; - statusDuration = 60f * 4; - shootEffect = Fx.shootSmallFlame; - hitEffect = Fx.hitFlameSmall; - despawnEffect = Fx.none; - status = StatusEffects.burning; - keepVelocity = false; - hittable = false; - } + basicFlame = new BulletType(3.35f, 15f){{ + ammoMultiplier = 3f; + hitSize = 7f; + lifetime = 18f; + pierce = true; + statusDuration = 60f * 4; + shootEffect = Fx.shootSmallFlame; + hitEffect = Fx.hitFlameSmall; + despawnEffect = Fx.none; + status = StatusEffects.burning; + keepVelocity = false; + hittable = false; + }}; - @Override - public float range(){ - return 50f; - } - }; - - pyraFlame = new BulletType(3.3f, 22f){{ + pyraFlame = new BulletType(3.35f, 22f){{ ammoMultiplier = 4f; hitSize = 7f; - lifetime = 42f; + lifetime = 18f; pierce = true; - drag = 0.05f; statusDuration = 60f * 6; shootEffect = Fx.shootPyraFlame; hitEffect = Fx.hitFlameSmall; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 2b5c563983..4bff4a1e7f 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -668,7 +668,7 @@ public class UnitTypes implements ContentList{ collidesTiles = false; ammoMultiplier = 4f; splashDamageRadius = 60f; - splashDamage = 80f; + splashDamage = 60f; backColor = Pal.missileYellowBack; frontColor = Pal.missileYellow; trailEffect = Fx.artilleryTrail; diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 47d0972b6c..d802236a53 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -50,10 +50,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I lookAt(x, y); } + public boolean inRange(Position other){ + return within(other, type.range); + } + public boolean hasWeapons(){ return type.hasWeapons(); } + public float range(){ + return type.range; + } + @Replace public float clipSize(){ return type.region.getWidth() * 2f; diff --git a/core/src/mindustry/entities/comp/WeaponsComp.java b/core/src/mindustry/entities/comp/WeaponsComp.java index 661346188f..ef199f01fd 100644 --- a/core/src/mindustry/entities/comp/WeaponsComp.java +++ b/core/src/mindustry/entities/comp/WeaponsComp.java @@ -24,7 +24,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{ /** weapon mount array, never null */ @SyncLocal WeaponMount[] mounts = {}; - @ReadOnly transient float range, aimX, aimY; + @ReadOnly transient float aimX, aimY; @ReadOnly transient boolean isRotate; boolean isShooting; 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){ mounts = new WeaponMount[def.weapons.size]; - range = def.range; for(int i = 0; i < mounts.length; 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.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta); - }else{ + }else if(!weapon.rotate){ mount.rotation = 0; mount.targetRotation = angleTo(mount.aimX, mount.aimY); } diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 3a9d43e2b2..5bbd462d42 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -5,42 +5,110 @@ import arc.math.geom.*; import arc.util.*; import mindustry.entities.*; import mindustry.gen.*; +import mindustry.type.*; import mindustry.world.*; import mindustry.world.meta.*; -import static mindustry.Vars.indexer; +import static mindustry.Vars.*; public class AIController implements UnitController{ protected static final Vec2 vec = new Vec2(); protected static final int timerTarget = 0; protected Unit unit; - protected Teamc target; 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)); } - protected void targetClosestAllyFlag(BlockFlag flag){ - Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team, flag)); - if(target != null) this.target = target.build; + @Override + public void updateUnit(){ + updateTargeting(); + updateMovement(); } - protected void targetClosestEnemyFlag(BlockFlag flag){ - Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team, flag)); - if(target != null) this.target = target.build; + protected void updateMovement(){ + + } + + 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(){ return timer.get(timerTarget, 30); } - protected void targetClosest(){ - 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())); - if(newTarget != null){ - target = newTarget; - } + protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){ + return target(x, y, range, air, ground); } protected void init(){ diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index e2702311c5..d0ac7b7f40 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -80,6 +80,7 @@ public class UnitType extends UnlockableContent{ public float trailX = 4f, trailY = -3f, trailScl = 1f; /** Whether the unit can heal blocks. Initialized in init() */ public boolean canHeal = false; + public boolean singleTarget = false; public ObjectSet immunities = new ObjectSet<>(); public Sound deathSound = Sounds.bang; @@ -180,10 +181,13 @@ public class UnitType extends UnlockableContent{ public void init(){ if(constructor == null) throw new IllegalArgumentException("no constructor set up for unit '" + name + "'"); + singleTarget = weapons.size <= 1; + //set up default range if(range < 0){ + range = Float.MAX_VALUE; for(Weapon weapon : weapons){ - range = Math.max(range, weapon.bullet.range()); + range = Math.min(range, weapon.bullet.range() + hitsize/2f); } }