diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java index 44950c34eb..786821eadb 100644 --- a/core/src/mindustry/ai/BlockIndexer.java +++ b/core/src/mindustry/ai/BlockIndexer.java @@ -16,6 +16,7 @@ import mindustry.gen.*; import mindustry.logic.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.*; import mindustry.world.blocks.environment.*; import mindustry.world.meta.*; @@ -30,22 +31,22 @@ public class BlockIndexer{ private int quadWidth, quadHeight; + //TODO: refactor to be field of state, and also refactor such that data is stored in teams when appropriate /** Stores all ore quadrants on the map. Maps ID to qX to qY to a list of tiles with that ore. */ private IntSeq[][][] ores, wallOres; - /** Stores all damaged tile entities by team. */ - private Seq[] damagedTiles = new Seq[Team.all.length]; /** All ores present on the map - can be wall or floor. */ private Seq allPresentOres = new Seq<>(); /** All ores available on this map. */ private ObjectIntMap allOres = new ObjectIntMap<>(), allWallOres = new ObjectIntMap<>(); /** Stores teams that are present here as tiles. */ private Seq activeTeams = new Seq<>(Team.class); + //TODO: move to TeamData /** Maps teams to a map of flagged tiles by flag. */ private Seq[][] flagMap = new Seq[Team.all.length][BlockFlag.all.length]; /** Counts whether a certain floor is present in the world upon load. */ private boolean[] blocksPresent; /** Array used for returning and reusing. */ - private Seq breturnArray = new Seq<>(Building.class); + private Seq breturn = new Seq<>(Building.class); /** Maps block flag to a list of floor tiles that have it. */ private Seq[] floorMap; @@ -66,7 +67,6 @@ public class BlockIndexer{ }); Events.on(WorldLoadEvent.class, event -> { - damagedTiles = new Seq[Team.all.length]; flagMap = new Seq[Team.all.length][BlockFlag.all.length]; floorMap = new Seq[BlockFlag.all.length]; activeTeams = new Seq<>(Team.class); @@ -214,9 +214,13 @@ public class BlockIndexer{ data.turretTree.remove(build); } + if(data.shieldTree != null && build instanceof ShieldProvider){ + data.shieldTree.remove(build); + } + //unregister damaged buildings if applicable - if(build.wasDamaged && damagedTiles[team.id] != null){ - damagedTiles[team.id].remove(build); + if(build.wasDamaged){ + data.damagedBuildings.remove(build); } build.wasDamaged = false; @@ -292,14 +296,9 @@ public class BlockIndexer{ /** Returns all damaged tiles by team. */ public Seq getDamaged(Team team){ - if(damagedTiles[team.id] == null){ - return damagedTiles[team.id] = new Seq<>(false); - } - - var tiles = damagedTiles[team.id]; - tiles.removeAll(b -> !b.damaged()); - - return tiles; + var buildings = team.data().damagedBuildings; + buildings.removeAll(b -> !b.damaged()); + return buildings; } /** Get all allied blocks with a flag. */ @@ -329,24 +328,24 @@ public class BlockIndexer{ }); return returnBool; }else{ - breturnArray.clear(); + breturn.clear(); var buildings = team.data().buildingTree; if(buildings == null) return false; buildings.intersect(wx - range, wy - range, range*2f, range*2f, b -> { if(b.within(wx, wy, range + b.hitSize() / 2f) && pred.get(b) && !b.block.privileged){ - breturnArray.add(b); + breturn.add(b); } }); } - int size = breturnArray.size; - var items = breturnArray.items; + int size = breturn.size; + var items = breturn.items; for(int i = 0; i < size; i++){ cons.get(items[i]); items[i] = null; } - breturnArray.size = 0; + breturn.size = 0; return size > 0; } @@ -355,30 +354,47 @@ public class BlockIndexer{ public boolean eachBlock(Team team, Rect rect, Boolf pred, Cons cons){ if(team == null) return false; - breturnArray.clear(); + breturn.clear(); var buildings = team.data().buildingTree; if(buildings == null) return false; buildings.intersect(rect, b -> { if(pred.get(b) && !b.block.privileged){ - breturnArray.add(b); + breturn.add(b); } }); - int size = breturnArray.size; - var items = breturnArray.items; + int size = breturn.size; + var items = breturn.items; for(int i = 0; i < size; i++){ cons.get(items[i]); items[i] = null; } - breturnArray.size = 0; + breturn.size = 0; return size > 0; } + /** @return all enemy shield blocks in a rectangle (bottom left corner origin). */ + public Seq getEnemyShields(Team team, float x, float y, float width, float height){ + breturn.clear(); + var all = state.teams.present; + for(int i = 0; i < all.size; i++){ + Team enemy = all.items[i].team; + if(enemy == team) continue; + + var data = enemy.data(); + if(data.shieldTree != null){ + data.shieldTree.intersect(x, y, width, height, breturn); + } + } + + return breturn; + } + /** Get all enemy blocks with a flag. */ public Seq getEnemy(Team team, BlockFlag type){ - breturnArray.clear(); + breturn.clear(); Seq data = state.teams.present; //when team data is not initialized, scan through every team. this is terrible if(data.isEmpty()){ @@ -386,7 +402,7 @@ public class BlockIndexer{ if(enemy == team || (enemy == Team.derelict && !state.rules.coreCapture)) continue; var set = getFlagged(enemy)[type.ordinal()]; if(set != null){ - breturnArray.addAll(set); + breturn.addAll(set); } } }else{ @@ -395,28 +411,26 @@ public class BlockIndexer{ if(enemy == team || (enemy == Team.derelict && !state.rules.coreCapture)) continue; var set = getFlagged(enemy)[type.ordinal()]; if(set != null){ - breturnArray.addAll(set); + breturn.addAll(set); } } } - return breturnArray; + return breturn; } public void notifyHealthChanged(Building build){ boolean damaged = build.damaged(); if(build.wasDamaged != damaged){ - if(damagedTiles[build.team.id] == null){ - damagedTiles[build.team.id] = new Seq<>(false); - } + var list = build.team.data().damagedBuildings; if(damaged){ //is now damaged, add to array - damagedTiles[build.team.id].add(build); + list.add(build); }else{ //no longer damaged, remove - damagedTiles[build.team.id].remove(build); + list.remove(build); } build.wasDamaged = damaged; @@ -424,16 +438,16 @@ public class BlockIndexer{ } public void allBuildings(float x, float y, float range, Cons cons){ - breturnArray.clear(); + breturn.clear(); for(int i = 0; i < activeTeams.size; i++){ Team team = activeTeams.items[i]; var buildings = team.data().buildingTree; if(buildings == null) continue; - buildings.intersect(x - range, y - range, range*2f, range*2f, breturnArray); + buildings.intersect(x - range, y - range, range*2f, range*2f, breturn); } - var items = breturnArray.items; - int size = breturnArray.size; + var items = breturn.items; + int size = breturn.size; for(int i = 0; i < size; i++){ var b = items[i]; if(b != null && b.within(x, y, range + b.hitSize()/2f)){ @@ -441,7 +455,7 @@ public class BlockIndexer{ } items[i] = null; } - breturnArray.size = 0; + breturn.size = 0; } public Building findEnemyTile(Team team, float x, float y, float range, BuildingPriorityf priority, Boolf pred){ @@ -492,11 +506,11 @@ public class BlockIndexer{ var buildings = team.data().buildingTree; if(buildings == null) return null; - breturnArray.clear(); - buildings.intersect(rect.setCentered(x, y, range * 2f), breturnArray); + breturn.clear(); + buildings.intersect(rect.setCentered(x, y, range * 2f), breturn); - for(int i = 0; i < breturnArray.size; i++){ - var next = breturnArray.items[i]; + for(int i = 0; i < breturn.size; i++){ + var next = breturn.items[i]; if(!pred.get(next) || (next.team != source && !next.block.targetable)) continue; @@ -622,6 +636,14 @@ public class BlockIndexer{ data.turretTree.insert(tile.build); } + if(tile.build instanceof ShieldProvider){ + if(data.shieldTree == null){ + data.shieldTree = new ShieldQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); + } + + data.shieldTree.insert(tile.build); + } + notifyHealthChanged(tile.build); } @@ -652,4 +674,21 @@ public class BlockIndexer{ return new TurretQuadtree(rect); } } + + static class ShieldQuadtree extends QuadTree{ + + public ShieldQuadtree(Rect bounds){ + super(bounds); + } + + @Override + public void hitbox(T build){ + tmp.setCentered(build.x, build.y, build.getShieldBounds()); + } + + @Override + protected QuadTree newChild(Rect rect){ + return new ShieldQuadtree<>(rect); + } + } } diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 50cce3cf86..4a99e4df30 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -110,7 +110,7 @@ public class Damage{ int f = i; Time.run(i * 2f, () -> { var shields = ignoreTeam == null ? null : indexer.getEnemy(ignoreTeam, BlockFlag.shield); - if(shields == null || shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, damagePerWave))){ + if(shields == null || shields.isEmpty() || !shields.contains(b -> b instanceof ShieldProvider s && s.absorbExplosion(x, y, damagePerWave))){ damage(ignoreTeam, x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), damagePerWave, false); } diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 2934e3a496..633a352515 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -28,9 +28,8 @@ import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.blocks.environment.*; import mindustry.world.blocks.payloads.*; -import mindustry.world.meta.*; -import static java.lang.Float.NaN; +import static java.lang.Float.*; import static mindustry.Vars.*; import static mindustry.logic.GlobalVars.*; @@ -922,9 +921,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I //if this unit crash landed (was flying), damage stuff in a radius if(type.flying && !spawnedByCore && type.createWreck && state.rules.unitCrashDamage(team) > 0){ - var shields = indexer.getEnemy(team, BlockFlag.shield); + var shields = indexer.getEnemyShields(team, x, y, 0f, 0f); float crashDamage = Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 2.5f * state.rules.unitCrashDamage(team); - if(shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, crashDamage))){ + if(shields.isEmpty() || !shields.contains(b -> b instanceof ShieldProvider s && s.absorbExplosion(x, y, crashDamage))){ Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, crashDamage, true, false, true); } } diff --git a/core/src/mindustry/game/Teams.java b/core/src/mindustry/game/Teams.java index 636feb6b36..f488bc59b7 100644 --- a/core/src/mindustry/game/Teams.java +++ b/core/src/mindustry/game/Teams.java @@ -295,6 +295,8 @@ public class Teams{ public @Nullable QuadTree buildingTree; /** Turrets by range. Null if not active. */ public @Nullable QuadTree turretTree; + /** ShieldProvider instances. */ + public @Nullable QuadTree shieldTree; /** Quadtree for units of this team. Do not access directly. */ public @Nullable QuadTree unitTree; /** Current unit cap. Do not modify externally. */ @@ -313,6 +315,8 @@ public class Teams{ public Seq buildings = new Seq<>(false); /** Units of this team by type. Updated each frame. */ public @Nullable Seq[] unitsByType; + /** Stores all damaged buildings. Do not access directly. */ + public Seq damagedBuildings = new Seq<>(); public TeamData(Team team){ this.team = team; diff --git a/core/src/mindustry/mod/ClassMap.java b/core/src/mindustry/mod/ClassMap.java index 116677d44b..d67a6605a3 100644 --- a/core/src/mindustry/mod/ClassMap.java +++ b/core/src/mindustry/mod/ClassMap.java @@ -1,11 +1,13 @@ package mindustry.mod; import arc.struct.*; +import mindustry.world.blocks.*; + /** Generated class. Maps simple class names to concrete classes. For use in JSON mods. */ @SuppressWarnings("deprecation") public class ClassMap{ public static final ObjectMap> classes = new ObjectMap<>(); - + static{ classes.put("AssemblerAI", mindustry.ai.types.AssemblerAI.class); classes.put("BoostAI", mindustry.ai.types.BoostAI.class); @@ -151,7 +153,7 @@ public class ClassMap{ classes.put("ConstructBlock", mindustry.world.blocks.ConstructBlock.class); classes.put("ConstructBuild", mindustry.world.blocks.ConstructBlock.ConstructBuild.class); classes.put("ControlBlock", mindustry.world.blocks.ControlBlock.class); - classes.put("ExplosionShield", mindustry.world.blocks.ExplosionShield.class); + classes.put("ExplosionShield", ShieldProvider.class); classes.put("ItemSelection", mindustry.world.blocks.ItemSelection.class); classes.put("LaunchAnimator", mindustry.world.blocks.LaunchAnimator.class); classes.put("RotBlock", mindustry.world.blocks.RotBlock.class); diff --git a/core/src/mindustry/world/blocks/ExplosionShield.java b/core/src/mindustry/world/blocks/ShieldProvider.java similarity index 59% rename from core/src/mindustry/world/blocks/ExplosionShield.java rename to core/src/mindustry/world/blocks/ShieldProvider.java index 892795632c..3b9ead0478 100644 --- a/core/src/mindustry/world/blocks/ExplosionShield.java +++ b/core/src/mindustry/world/blocks/ShieldProvider.java @@ -1,7 +1,8 @@ package mindustry.world.blocks; -//TODO: horrible API design, but I'm not sure of a better way to do this right now. please don't use this class -public interface ExplosionShield{ +public interface ShieldProvider{ /** @return whether the shield was able to absorb the explosion; this should apply damage to the shield if true is returned. */ boolean absorbExplosion(float x, float y, float damage); + /** @return maximum (not current size!) half-size of the shield square bounding box */ + float getShieldBounds(); } diff --git a/core/src/mindustry/world/blocks/defense/ForceProjector.java b/core/src/mindustry/world/blocks/defense/ForceProjector.java index 91567ee40b..9f94ddae2b 100644 --- a/core/src/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/mindustry/world/blocks/defense/ForceProjector.java @@ -162,7 +162,7 @@ public class ForceProjector extends Block{ Draw.color(); } - public class ForceBuild extends Building implements Ranged, ExplosionShield{ + public class ForceBuild extends Building implements Ranged, ShieldProvider{ public boolean broken = true; public float buildup, radscl, hit, warmup, phaseHeat; @@ -277,6 +277,11 @@ public class ForceProjector extends Block{ return absorb; } + @Override + public float getShieldBounds(){ + return radius + phaseRadiusBoost; + } + public float realRadius(){ return (radius + phaseHeat * phaseRadiusBoost) * radscl; }