diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 76c5b827a5..33b1025ef2 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3652,7 +3652,7 @@ public class Blocks{ size = 1; health = 260; shootSound = Sounds.shootArc; - + consumePower(3.3f); coolant = consumeCoolant(0.1f); coolantMultiplier = 30f; @@ -4014,6 +4014,8 @@ public class Blocks{ frontColor = Pal.siliconAmmoFront; lifeScaleRandMax = 1.08f; lifeScaleRandMin = 0.95f; + + unitSort = UnitSorts.closest; }}, Items.pyratite, new ArtilleryBulletType(3f, 48){{ hitEffect = new MultiEffect(Fx.blastExplosion, Fx.shockwave); @@ -4091,7 +4093,6 @@ public class Blocks{ ammoPerShot = 2; velocityRnd = 0.2f; - scaleLifetimeOffset = 1f / 9f; recoil = 6f; shake = 2f; range = 290f; @@ -4101,6 +4102,8 @@ public class Blocks{ scaledHealth = 130; depositCooldown = 2.0f; shootSound = Sounds.shootRipple; + scaleLifetimeOffset = 1f / 10f; + unitSort = UnitSorts.grouped; }}; cyclone = new ItemTurret("cyclone"){{ @@ -4694,6 +4697,8 @@ public class Blocks{ shrinkX = 0.2f; shrinkY = 0.1f; buildingDamageMultiplier = 0.3f; + + unitSort = UnitSorts.grouped; }}, Items.carbide, new ArtilleryBulletType(3.25f, 700, "shell"){{ hitEffect = new MultiEffect(Fx.titanExplosionSmall, Fx.titanSmokeSmall); @@ -4732,6 +4737,8 @@ public class Blocks{ buildingDamageMultiplier = 0.2f; fragLifeMin = 1.5f; + unitSort = UnitSorts.strongest; + fragBullets = 12; fragBullet = new ArtilleryBulletType(0.5f, 50, "shell"){{ hitEffect = new MultiEffect(Fx.titanExplosionFrag, Fx.titanLightSmall, new WaveEffect(){{ @@ -4808,6 +4815,8 @@ public class Blocks{ buildingDamageMultiplier = 0f; }}; }}; + + unitSort = UnitSorts.grouped; }} ); diff --git a/core/src/mindustry/entities/UnitSorts.java b/core/src/mindustry/entities/UnitSorts.java index 392e6757d1..d9dc643a6d 100644 --- a/core/src/mindustry/entities/UnitSorts.java +++ b/core/src/mindustry/entities/UnitSorts.java @@ -15,10 +15,16 @@ public class UnitSorts{ mostArmor = (u, x, y) -> -u.armor + Mathf.dst2(u.x, u.y, x, y) / 6400f, leastArmor = (u, x, y) -> u.armor + Mathf.dst2(u.x, u.y, x, y) / 6400f, mostShield = (u, x, y) -> -u.shield + Mathf.dst2(u.x, u.y, x, y) / 6400f, - leastShield = (u, x, y) -> u.shield + Mathf.dst2(u.x, u.y, x, y) / 6400f; + leastShield = (u, x, y) -> u.shield + Mathf.dst2(u.x, u.y, x, y) / 6400f, + grouped = grouped(0.003f); public static BuildingPriorityf buildingDefault = b -> b.block.priority, buildingWater = b -> b.block.priority + (b.liquids != null && b.liquids.get(Liquids.water) > 5f ? 10f : 0f); + + /** @param dstWeight higher values make distance more important. Set to <= 0 to ignore distance. Ignores flying units. */ + public static Sortf grouped(float dstWeight){ + return (u, x, y) -> -u.team.data().getClustered(u.x, u.y) + (dstWeight > 0 ? Mathf.dst(u.x, u.y, x, y) * dstWeight : 0f); + } } \ No newline at end of file diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index ee5148dda2..7086570513 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -14,6 +14,7 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.ctype.*; import mindustry.entities.*; +import mindustry.entities.Units.*; import mindustry.entities.part.*; import mindustry.entities.pattern.*; import mindustry.game.EventType.*; @@ -120,6 +121,8 @@ public class BulletType extends Content implements Cloneable{ public StatusEffect status = StatusEffects.none; /** Intensity of applied status effect in terms of duration. */ public float statusDuration = 60 * 8f; + /** Turret only. Function for choosing which unit to target. Overrides turret sorting */ + public Sortf unitSort = UnitSorts.closest; /** Chance for this bullet to apply a status effect */ public float statusChance = 1f; /** Turret only. If false, blocks will not be targeted. */ diff --git a/core/src/mindustry/game/Teams.java b/core/src/mindustry/game/Teams.java index c15a5b7f38..636feb6b36 100644 --- a/core/src/mindustry/game/Teams.java +++ b/core/src/mindustry/game/Teams.java @@ -269,6 +269,7 @@ public class Teams{ public static class TeamData{ private static final IntSeq derelictBuffer = new IntSeq(); + private static final float clusterChunkSize = 70f; public final Team team; @@ -278,6 +279,8 @@ public class Teams{ public @Nullable RtsAI rtsAi; private boolean presentFlag; + private IntIntMap clusteredCounts = new IntIntMap(); + private float lastClusterUpdateTimer = -100f; /** Enemies with cores or spawn points. */ public Team[] coreEnemies = {}; @@ -470,6 +473,26 @@ public class Teams{ return team.rules().rtsAi || team.rules().buildAi; } + /** @return approximate number of clustered ground units at a specific position */ + public int getClustered(float x, float y){ + //update based on ticks passed (no increment) + if(Time.time > lastClusterUpdateTimer + 10f){ + lastClusterUpdateTimer = Time.time; + clusteredCounts.clear(); + units.each(u -> { + //clusters are for artillery, which can't hit flying units + if(!u.isFlying()){ + clusteredCounts.increment(clusterKey(u.x, u.y)); + } + }); + } + return clusteredCounts.get(clusterKey(x, y)); + } + + private static int clusterKey(float x, float y){ + return ((Mathf.floor(x / clusterChunkSize) & 0xFFF) << 12) | (Mathf.floor(y / clusterChunkSize) & 0xFFF); + } + @Override public String toString(){ return "TeamData{" + diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index e48aba0dbe..af98c295d0 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -639,14 +639,15 @@ public class Turret extends ReloadTurret{ } protected Posc findEnemy(float range){ + var ammo = peekAmmo(); + Sortf sort = ammo.unitSort != UnitSorts.closest ? ammo.unitSort : unitSort; if(targetAir && !targetGround){ - return Units.bestEnemy(team, x, y, range, e -> !e.dead() && !e.isGrounded() && unitFilter.get(e), unitSort); + return Units.bestEnemy(team, x, y, range, e -> !e.dead() && !e.isGrounded() && unitFilter.get(e), sort); }else{ - var ammo = peekAmmo(); boolean buildings = targetGround && targetBlocks && (ammo == null || ammo.targetBlocks), missiles = ammo == null || ammo.targetMissiles; return Units.bestTarget(team, x, y, range, e -> !e.dead() && unitFilter.get(e) && (e.isGrounded() || targetAir) && (!e.isGrounded() || targetGround) && (missiles || !(e instanceof TimedKillc)), - b -> buildings && buildingFilter.get(b), unitSort); + b -> buildings && buildingFilter.get(b), sort); } }