From 7591c51b606c314fa82ddfe1c006e6f5e9174960 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Sep 2026 21:12:44 -0400 Subject: [PATCH 1/4] More dummy validation + Pathfinder fixes --- core/src/mindustry/ai/ControlPathfinder.java | 6 +++++- core/src/mindustry/ai/types/CommandAI.java | 3 ++- .../mindustry/world/blocks/defense/TargetDummy.java | 10 ++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index feeca983d9..60def58dd4 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -164,6 +164,7 @@ public class ControlPathfinder implements Runnable{ //main thread only! long lastUpdateId = state.updateId; + long lastRecomputeTime; //both threads volatile boolean notFound = false; @@ -1192,7 +1193,9 @@ public class ControlPathfinder implements Runnable{ long fieldKey = FieldIndex.get(destPos, costId, team); //use existing request if it exists. - if(request != null && request.destination == destPos){ + if(request != null && (request.destination == destPos || + //can only recompute path only twice a second, unless it's far away + (Time.timeSinceMillis(request.lastRecomputeTime) < 1000 && Mathf.dst(destX, destY, request.destination % wwidth, request.destination / wwidth) <= 4f))){ request.lastUpdateId = state.updateId; Tile initialTileOn = tileOn; @@ -1313,6 +1316,7 @@ public class ControlPathfinder implements Runnable{ unitRequests.put(unit, request = new PathRequest(unit, team, costId, destPos)); PathRequest f = request; + request.lastRecomputeTime = Time.millis(); //on the pathfinding thread: initialize the request queue.post(() -> { diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 637ade5f29..3d955a5b6e 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -259,7 +259,8 @@ public class CommandAI extends AIController{ targetPos = new Vec2(); lastTargetPos = targetPos; } - targetPos.set(attackTarget); + //don't target where target dummies are right now, target their block + targetPos.set(attackTarget instanceof TargetDummyUnit t && t.building != null ? t.building : attackTarget); if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.type.pathCostId != ControlPathfinder.costIdLegs && !ramming){ Tile best = build.findClosestEdge(unit, Tile::solid); diff --git a/core/src/mindustry/world/blocks/defense/TargetDummy.java b/core/src/mindustry/world/blocks/defense/TargetDummy.java index 851d988e88..93e89fdb57 100644 --- a/core/src/mindustry/world/blocks/defense/TargetDummy.java +++ b/core/src/mindustry/world/blocks/defense/TargetDummy.java @@ -22,6 +22,8 @@ import mindustry.world.meta.*; import static mindustry.Vars.*; public class TargetDummy extends Block{ + static final float maxDummySize = 30f; + public final int dpsUpdateTime = timers++; public UnitType unitType = UnitTypes.dummy; public float pullScale = 0.33f; @@ -39,7 +41,6 @@ public class TargetDummy extends Block{ targetable = false; allowedInPayloads = false; canOverdrive = false; - saveConfig = true; config(int[].class, (TargetDummyBuild tile, int[] config) -> { @@ -47,9 +48,10 @@ public class TargetDummy extends Block{ tile.unitTeam = tile.team; if(config[0] == 1) tile.unitTeam = Team.get(tile.dummyTeam()); tile.boosting = config[1] == 1; - tile.unitArmor = config[2]; + tile.unitArmor = Mathf.clamp(config[2], 0, 100_000); tile.resetTime = config[3]; - tile.dummySize = Float.intBitsToFloat(config[4]); + tile.dummySize = Mathf.clamp(Float.intBitsToFloat(config[4]), 0.1f, maxDummySize * tilesize); + if(Float.isNaN(tile.dummySize) || Float.isInfinite(tile.dummySize)) tile.dummySize = 8f; }); } @@ -251,7 +253,7 @@ public class TargetDummy extends Block{ t.field("" + (dummySize/tilesize), TextFieldFilter.floatsOnly, s -> configureInt(4, Float.floatToIntBits(Strings.parseFloat(s) * tilesize))) .valid(val -> { float parsed = Strings.parseFloat(val, Float.MAX_VALUE); - return parsed >= 0.1f && parsed <= 50f; + return parsed >= 0.1f && parsed <= maxDummySize; }).padLeft(8f).growX(); t.add(StatUnit.blocks.localized()).padLeft(8f); }).top().grow().margin(8f); From 4368c6e60eece15bb15db22f66f88b42d00bef79 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Sep 2026 21:20:47 -0400 Subject: [PATCH 2/4] Fixed #12657 --- .../world/blocks/power/ThermalGenerator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/mindustry/world/blocks/power/ThermalGenerator.java b/core/src/mindustry/world/blocks/power/ThermalGenerator.java index f2c60402d1..ca57884ce7 100644 --- a/core/src/mindustry/world/blocks/power/ThermalGenerator.java +++ b/core/src/mindustry/world/blocks/power/ThermalGenerator.java @@ -43,6 +43,15 @@ public class ThermalGenerator extends PowerGenerator{ lightClipSize = Math.max(lightClipSize, 45f * size * 2f * 2f); } + @Override + public void afterPatch(){ + super.afterPatch(); + if(outputLiquid != null){ + outputsLiquid = true; + hasLiquids = true; + } + } + @Override public void setStats(){ super.setStats(); @@ -82,7 +91,7 @@ public class ThermalGenerator extends PowerGenerator{ generateEffect.at(x + Mathf.range(3f), y + Mathf.range(3f)); } - if(outputLiquid != null){ + if(outputLiquid != null && hasLiquids){ float added = Math.min(productionEfficiency * delta() * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid)); liquids.add(outputLiquid.liquid, added); dumpLiquid(outputLiquid.liquid); From 22ca0ce33fa5f195a549c781a4e99dfc68bf77c3 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Sep 2026 22:53:38 -0400 Subject: [PATCH 3/4] Avoidance radius clamp --- core/src/mindustry/async/AvoidanceProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/mindustry/async/AvoidanceProcess.java b/core/src/mindustry/async/AvoidanceProcess.java index 08b07d6093..2a5e6728eb 100644 --- a/core/src/mindustry/async/AvoidanceProcess.java +++ b/core/src/mindustry/async/AvoidanceProcess.java @@ -92,7 +92,7 @@ public class AvoidanceProcess implements AsyncProcess{ float rad = Float.intBitsToFloat(items[i + 1]); float rad2 = rad * rad; - int r = Math.max(1, Mathf.ceil(rad)); + int r = Mathf.clamp(1, Mathf.ceil(rad), 20); for(int dx = -r; dx <= r; dx++){ for(int dy = -r; dy <= r; dy++){ From b76147ec5030c5458b19678f387724f3d1c18ba4 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Sep 2026 10:48:08 -0400 Subject: [PATCH 4/4] Antialias + linear filter bleed on data patch images --- core/src/mindustry/mod/DataImagePacker.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/mindustry/mod/DataImagePacker.java b/core/src/mindustry/mod/DataImagePacker.java index 6ddda14698..52a70a76d7 100644 --- a/core/src/mindustry/mod/DataImagePacker.java +++ b/core/src/mindustry/mod/DataImagePacker.java @@ -12,6 +12,7 @@ import arc.util.*; import arc.util.Log.*; import mindustry.*; import mindustry.game.EventType.*; +import mindustry.graphics.*; import mindustry.mod.data.*; import java.io.*; @@ -106,6 +107,8 @@ public class DataImagePacker{ try{ Pixmap pixmap = new Pixmap(cacheFile); + Pixmaps.antialias(pixmap); + Drawf.checkBleed(pixmap); //don't add the double dp prefix, only add it if it's not already present String name = (image.isGenerated() && image.name.contains("-dp-") ? "" : regionPrefix) + image.name;