diff --git a/core/assets-raw/sprites/blocks/defense/regen-projector-mid.png b/core/assets-raw/sprites/blocks/defense/regen-projector-mid.png new file mode 100644 index 0000000000..d5f5c37581 Binary files /dev/null and b/core/assets-raw/sprites/blocks/defense/regen-projector-mid.png differ diff --git a/core/assets-raw/sprites/blocks/defense/regen-projector-side-glow.png b/core/assets-raw/sprites/blocks/defense/regen-projector-side-glow.png deleted file mode 100644 index b8288fe7c4..0000000000 Binary files a/core/assets-raw/sprites/blocks/defense/regen-projector-side-glow.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/defense/regen-projector-top1.png b/core/assets-raw/sprites/blocks/defense/regen-projector-top1.png deleted file mode 100644 index 3d09b25c2d..0000000000 Binary files a/core/assets-raw/sprites/blocks/defense/regen-projector-top1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/defense/regen-projector-top2.png b/core/assets-raw/sprites/blocks/defense/regen-projector-top2.png deleted file mode 100644 index 6993d3239a..0000000000 Binary files a/core/assets-raw/sprites/blocks/defense/regen-projector-top2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/defense/regen-projector.png b/core/assets-raw/sprites/blocks/defense/regen-projector.png index 46230dd168..3d610ed2ae 100644 Binary files a/core/assets-raw/sprites/blocks/defense/regen-projector.png and b/core/assets-raw/sprites/blocks/defense/regen-projector.png differ diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java index 19c3d0f206..f39358d853 100644 --- a/core/src/mindustry/ai/BlockIndexer.java +++ b/core/src/mindustry/ai/BlockIndexer.java @@ -236,6 +236,29 @@ public class BlockIndexer{ return size > 0; } + /** Does not work with null teams. */ + public boolean eachBlock(Team team, Rect rect, Boolf pred, Cons cons){ + breturnArray.clear(); + + var buildings = team.data().buildings; + if(buildings == null) return false; + buildings.intersect(rect, b -> { + if(pred.get(b)){ + breturnArray.add(b); + } + }); + + int size = breturnArray.size; + var items = breturnArray.items; + for(int i = 0; i < size; i++){ + cons.get(items[i]); + items[i] = null; + } + breturnArray.size = 0; + + return size > 0; + } + /** Get all enemy blocks with a flag. */ public Seq getEnemy(Team team, BlockFlag type){ breturnArray.clear(); diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 7ec6f65382..aab855be8c 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -1527,7 +1527,7 @@ public class Blocks{ }}; buildTower = new BuildTurret("build-tower"){{ - requirements(Category.effect, with(Items.silicon, 60, Items.tungsten, 60, Items.oxide, 40)); + requirements(Category.effect, with(Items.silicon, 80, Items.carbide, 30, Items.oxide, 40, Items.thorium, 30)); outlineColor = Pal.darkOutline; consumes.power(3f); range = 150f; @@ -1538,21 +1538,25 @@ public class Blocks{ //TODO green looks bad switch to orange //TODO orange also looks bad hhhh regenProjector = new RegenProjector("regen-projector"){{ - requirements(Category.effect, with(Items.silicon, 60, Items.tungsten, 60, Items.oxide, 40)); + requirements(Category.effect, with(Items.silicon, 60, Items.tungsten, 60, Items.oxide, 30)); size = 3; consumes.power(1f); - rangeWidth = 6; - rangeLength = 22; + range = 28; consumes.liquid(Liquids.hydrogen, 1f / 60f); healPercent = 4f / 60f; - drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(Liquids.hydrogen, 9f / 4f), new DrawSideRegion(true), new DrawGlowRegion(){{ - //color = Color.valueOf("1eff21"); - }}, new DrawGlowRegion(true){{ - suffix = "-side-glow"; - alpha = 1f; + drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(Liquids.hydrogen, 9f / 4f), new DrawBlock(), new DrawGlowRegion(){{ + color = Color.orange;//Color.valueOf("1eff21"); + }}, new DrawPulseShape(false){{ + //radiusScl = 0.95f; + layer = Layer.effect; + }}, new DrawShape(){{ + layer = Layer.effect; + radius = 3.5f; + useWarmupRadius = true; + timeScl = 2f; }}); }}; diff --git a/core/src/mindustry/content/ErekirTechTree.java b/core/src/mindustry/content/ErekirTechTree.java index 7caac5a5b9..40f7702036 100644 --- a/core/src/mindustry/content/ErekirTechTree.java +++ b/core/src/mindustry/content/ErekirTechTree.java @@ -69,10 +69,12 @@ public class ErekirTechTree{ }); - //TODO more tiers of build tower or "support" structures like overdrive projectors - //TODO method of repairing blocks of damage - node(buildTower, () -> { + node(regenProjector, () -> { + //TODO more tiers of build tower or "support" structures like overdrive projectors + node(buildTower, () -> { + + }); }); }); }); diff --git a/core/src/mindustry/game/SpawnGroup.java b/core/src/mindustry/game/SpawnGroup.java index 1d70384a47..cc5879ca13 100644 --- a/core/src/mindustry/game/SpawnGroup.java +++ b/core/src/mindustry/game/SpawnGroup.java @@ -42,6 +42,8 @@ public class SpawnGroup implements JsonSerializable, Cloneable{ public int unitAmount = 1; /** If not -1, the unit will only spawn in spawnpoints with these packed coordinates. */ public int spawn = -1; + /** Fraction of health that unit is spawned with. */ + public float healthFraction = 1f; /** Seq of payloads that this unit will spawn with. */ public @Nullable Seq payloads; /** Status effect applied to the spawned unit. Null to disable. */ @@ -91,6 +93,7 @@ public class SpawnGroup implements JsonSerializable, Cloneable{ } unit.shield = getShield(wave); + unit.health = unit.maxHealth * healthFraction; //load up spawn payloads if(payloads != null && unit instanceof Payloadc pay){ @@ -118,6 +121,7 @@ public class SpawnGroup implements JsonSerializable, Cloneable{ if(unitAmount != 1) json.writeValue("amount", unitAmount); if(effect != null) json.writeValue("effect", effect.name); if(spawn != -1) json.writeValue("spawn", spawn); + if(healthFraction != 1f) json.writeValue("healthFraction", healthFraction); if(payloads != null && payloads.size > 0){ json.writeValue("payloads", payloads.map(u -> u.name).toArray(String.class)); } @@ -138,6 +142,7 @@ public class SpawnGroup implements JsonSerializable, Cloneable{ shieldScaling = data.getFloat("shieldScaling", 0); unitAmount = data.getInt("amount", 1); spawn = data.getInt("spawn", -1); + healthFraction = data.getFloat("healthFraction", 1f); if(data.has("payloads")){ payloads = Seq.with(json.readValue(String[].class, data.get("payloads"))).map(s -> content.getByName(ContentType.unit, s)); } diff --git a/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java b/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java index ec30788a27..261ed2b7b5 100644 --- a/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java @@ -255,12 +255,13 @@ public class ErekirPlanetGenerator extends PlanetGenerator{ state.rules.waves = true; state.rules.showSpawns = true; state.rules.waveTimer = true; - state.rules.waveSpacing = 60f * 60f * 15f; + state.rules.waveSpacing = 60f * 60f * 7.5f; state.rules.spawns = Seq.with(new SpawnGroup(){{ type = UnitTypes.vanquish; spacing = 1; shieldScaling = 60; unitScaling = 2f; + healthFraction = 0.2f; }}); } } diff --git a/core/src/mindustry/type/Liquid.java b/core/src/mindustry/type/Liquid.java index d6a4e2a380..15e6a81be4 100644 --- a/core/src/mindustry/type/Liquid.java +++ b/core/src/mindustry/type/Liquid.java @@ -22,7 +22,7 @@ public class Liquid extends UnlockableContent{ protected static final Rand rand = new Rand(); - /** TODO If true, this fluid is treated as a gas (and does not create puddles) */ + /** If true, this fluid is treated as a gas (and does not create puddles) */ public boolean gas = false; /** Color used in pipes and on the ground. */ public Color color; diff --git a/core/src/mindustry/world/blocks/defense/RegenProjector.java b/core/src/mindustry/world/blocks/defense/RegenProjector.java index 28064eb393..5292c6d104 100644 --- a/core/src/mindustry/world/blocks/defense/RegenProjector.java +++ b/core/src/mindustry/world/blocks/defense/RegenProjector.java @@ -3,7 +3,6 @@ package mindustry.world.blocks.defense; import arc.*; import arc.graphics.g2d.*; import arc.math.*; -import arc.math.geom.*; import arc.struct.*; import arc.util.*; import mindustry.content.*; @@ -23,27 +22,19 @@ public class RegenProjector extends Block{ private static final IntFloatMap mendMap = new IntFloatMap(); private static long lastUpdateFrame = -1; - //cached points per-block for drawing convenience - protected @Nullable Vec2[] drawPoints; - - public int rangeLength = 14, rangeWidth = 5; + public int range = 14; //per frame public float healPercent = 12f / 60f; public DrawBlock drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawSideRegion(true)); - public float effectChance = 0.011f; + public float effectChance = 0.013f; public Effect effect = Fx.regenParticle; - public float beamSpacing = 60f * 7f, beamWidening = 5f, beamLenScl = 1.2f, beamStroke = 2f; - public Interp beamInterp = Interp.pow2Out; - public int beams = 6; - public RegenProjector(String name){ super(name); solid = true; update = true; - rotate = true; group = BlockGroup.projectors; hasPower = true; hasItems = true; @@ -56,28 +47,7 @@ public class RegenProjector extends Block{ public void drawPlace(int x, int y, int rotation, boolean valid){ super.drawPlace(x, y, rotation, valid); - drawBounds(x * tilesize + offset, y * tilesize + offset, rotation); - } - - public void drawBounds(float x, float y, int rotation){ - if(drawPoints == null){ - drawPoints = new Vec2[]{ - new Vec2(1f, -rangeWidth), - new Vec2(1f + rangeLength, -rangeWidth), - new Vec2(1f + rangeLength, rangeWidth), - new Vec2(1f + 0f, rangeWidth), - }; - for(var v : drawPoints){ - v.scl(tilesize); - } - } - - for(int i = 0; i < 4; i++){ - Tmp.v1.set(drawPoints[i]).rotate(rotation * 90).add(x, y); - Tmp.v2.set(drawPoints[(i + 1) % 4]).rotate(rotation * 90).add(x, y); - - Drawf.dashLine(Pal.accent, Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y); - } + Drawf.dashSquare(Pal.accent, x, y, range * tilesize); } @Override @@ -105,25 +75,12 @@ public class RegenProjector extends Block{ public Seq targets = new Seq<>(); public int lastChange = -2; public float warmup, totalTime; + public boolean didRegen = false; public void updateTargets(){ targets.clear(); taken.clear(); - float rot = rotation * 90; - - for(int cy = -rangeWidth; cy <= rangeWidth; cy++){ - for(int cx = 1; cx <= rangeLength + 1; cx++){ - - //TODO handle offset - float wx = x/tilesize + Angles.trnsx(rot, cx, cy), wy = y/tilesize + Angles.trnsy(rot, cx, cy); - - Building build = world.build((int)wx, (int)wy); - - if(build != null && build.team == team && taken.add(build.id)){ - targets.add(build); - } - } - } + indexer.eachBlock(team, Tmp.r1.setCentered(x, y, range * tilesize), b -> true, targets::add); } @Override @@ -133,14 +90,18 @@ public class RegenProjector extends Block{ updateTargets(); } - warmup = Mathf.approachDelta(warmup, consValid() ? 1f : 0f, 1f / 70f); + //TODO should warmup depend on didRegen? + warmup = Mathf.approachDelta(warmup, consValid() && didRegen ? 1f : 0f, 1f / 70f); totalTime += warmup * Time.delta; + didRegen = false; if(consValid()){ //use Math.max to prevent stacking for(Building build : targets){ if(!build.damaged()) continue; + didRegen = true; + int pos = build.pos(); //TODO periodic effect float value = mendMap.get(pos); @@ -165,10 +126,19 @@ public class RegenProjector extends Block{ } } + @Override + public boolean productionValid(){ + return didRegen; + } + @Override public void drawSelect(){ super.drawSelect(); - drawBounds(x, y, rotation); + + Drawf.dashSquare(Pal.accent, x, y, range * tilesize); + for(var target : targets){ + Drawf.selected(target, Pal.accent); + } } @Override @@ -184,26 +154,6 @@ public class RegenProjector extends Block{ @Override public void draw(){ drawer.drawBase(this); - - for(int i = 0; i < beams; i++){ - float life = beamInterp.apply((totalTime / beamSpacing + i / (float)beams) % 1f); - float len = life * rangeLength*beamLenScl * tilesize + size * tilesize/2f; - float width = Math.min(life * rangeWidth * 2f * tilesize * beamWidening, rangeWidth * 2f * tilesize); - float stroke = (0.5f + beamStroke * life) * warmup; - Draw.z(Layer.effect); - - Lines.stroke(stroke, Pal.accent); - Draw.alpha(1f - Mathf.curve(life, 0.5f)); - Lines.lineAngleCenter( - x + Angles.trnsx(rotdeg(), len), - y + Angles.trnsy(rotdeg(), len), - rotdeg() + 90f, - width - ); - - Draw.reset(); - } - } @Override diff --git a/core/src/mindustry/world/draw/DrawGlowRegion.java b/core/src/mindustry/world/draw/DrawGlowRegion.java index 3bcf71cf02..445183b4d1 100644 --- a/core/src/mindustry/world/draw/DrawGlowRegion.java +++ b/core/src/mindustry/world/draw/DrawGlowRegion.java @@ -32,6 +32,11 @@ public class DrawGlowRegion extends DrawBlock{ this.rotate = rotate; } + + public DrawGlowRegion(String suffix){ + this.suffix = suffix; + } + @Override public void drawBase(Building build){ if(build.warmup() <= 0.001f) return; diff --git a/core/src/mindustry/world/draw/DrawPartial.java b/core/src/mindustry/world/draw/DrawPartial.java new file mode 100644 index 0000000000..96ef7fccc7 --- /dev/null +++ b/core/src/mindustry/world/draw/DrawPartial.java @@ -0,0 +1,11 @@ +package mindustry.world.draw; + +import arc.util.*; +import mindustry.entities.units.*; +import mindustry.world.*; + +public abstract class DrawPartial extends DrawBlock{ + + @Override + public void drawPlan(Block block, BuildPlan plan, Eachable list){} +} diff --git a/core/src/mindustry/world/draw/DrawPulseShape.java b/core/src/mindustry/world/draw/DrawPulseShape.java new file mode 100644 index 0000000000..2535e7ef31 --- /dev/null +++ b/core/src/mindustry/world/draw/DrawPulseShape.java @@ -0,0 +1,55 @@ +package mindustry.world.draw; + +import arc.graphics.*; +import arc.graphics.g2d.*; +import arc.math.*; +import arc.math.geom.*; +import arc.util.*; +import mindustry.gen.*; +import mindustry.graphics.*; + +import static mindustry.Vars.*; + +public class DrawPulseShape extends DrawPartial{ + public Color color = Pal.accent.cpy(); + public float stroke = 2f, timeScl = 100f, minStroke = 0.2f; + public float radiusScl = 1f; + public float layer = -1f; + public boolean square = true; + + public DrawPulseShape(boolean square){ + this.square = square; + } + + public DrawPulseShape(){ + } + + @Override + public void drawBase(Building build){ + float pz = Draw.z(); + if(layer > 0) Draw.z(layer); + + float f = 1f - (Time.time / timeScl) % 1f; + float rad = build.block.size * tilesize / 2f * radiusScl; + + Draw.color(color); + Lines.stroke((stroke * f + minStroke) * build.warmup()); + + if(square){ + Lines.square(build.x, build.y, Math.min(1f + (1f - f) * rad, rad)); + }else{ + float r = Math.max(0f, Mathf.clamp(2f - f * 2f) * rad - f - 0.2f), w = Mathf.clamp(0.5f - f) * rad * 2f; + Lines.beginLine(); + for(int i = 0; i < 4; i++){ + Lines.linePoint(build.x + Geometry.d4(i).x * r + Geometry.d4(i).y * w, build.y + Geometry.d4(i).y * r - Geometry.d4(i).x * w); + if(f < 0.5f) Lines.linePoint(build.x + Geometry.d4(i).x * r - Geometry.d4(i).y * w, build.y + Geometry.d4(i).y * r + Geometry.d4(i).x * w); + } + Lines.endLine(true); + } + + + + Draw.reset(); + Draw.z(pz); + } +} diff --git a/core/src/mindustry/world/draw/DrawShape.java b/core/src/mindustry/world/draw/DrawShape.java new file mode 100644 index 0000000000..f7a5299116 --- /dev/null +++ b/core/src/mindustry/world/draw/DrawShape.java @@ -0,0 +1,25 @@ +package mindustry.world.draw; + +import arc.graphics.*; +import arc.graphics.g2d.*; +import mindustry.gen.*; +import mindustry.graphics.*; + +public class DrawShape extends DrawPartial{ + public Color color = Pal.accent.cpy(); + public int sides = 4; + public float radius = 2f, timeScl = 1f, layer = -1f, x, y; + public boolean useWarmupRadius = false; + + @Override + public void drawBase(Building build){ + float pz = Draw.z(); + if(layer > 0) Draw.z(layer); + + Draw.color(color); + Fill.poly(build.x + x, build.y + y, sides, useWarmupRadius ? radius * build.warmup() : radius, build.totalProgress() * timeScl); + + Draw.reset(); + Draw.z(pz); + } +}