diff --git a/core/assets-raw/sprites/blocks/turrets/afflict/afflict.png b/core/assets-raw/sprites/blocks/turrets/afflict/afflict.png new file mode 100644 index 0000000000..d62f5abc79 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/afflict/afflict.png differ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 6406ab4f88..cf6be1cb93 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -844,7 +844,6 @@ bar.output = Output bar.strength = [stat]{0}[lightgray]x strength units.processorcontrol = [lightgray]Processor Controlled -units.nocontroller = [lightgray]\ue815 No Controller bullet.damage = [stat]{0}[lightgray] damage bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index c344d4d746..9c9a4e812b 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -19,7 +19,7 @@ import static mindustry.ai.Pathfinder.*; //TODO I'm sure this class has countless problems public class ControlPathfinder implements Runnable{ private static final long maxUpdate = Time.millisToNanos(20); - private static final int updateFPS = 40; + private static final int updateFPS = 60; private static final int updateInterval = 1000 / updateFPS; public static boolean showDebug = false; @@ -151,6 +151,7 @@ public class ControlPathfinder implements Runnable{ req.destination.set(destination); req.curId = pathId; req.lastUpdateId = state.updateId; + req.lastPos.set(unit); req.lastWorldUpdate = worldUpdateId; requests.put(unit, req); @@ -170,6 +171,16 @@ public class ControlPathfinder implements Runnable{ req.destination.set(destination); req.curId = pathId; + //check for the unit getting stuck every N seconds + if((req.stuckTimer += Time.delta) >= 60f * 5f){ + req.stuckTimer = 0f; + //force recalculate + if(req.lastPos.within(unit, 1f)){ + req.lastWorldUpdate = -1; + } + req.lastPos.set(unit); + } + if(req.done){ int[] items = req.result.items; int len = req.result.size; @@ -193,11 +204,9 @@ public class ControlPathfinder implements Runnable{ req.rayPathIndex = req.pathIndex; } - //TODO indecision dance: moving forward blocks the raycasted node from view, so it moves back. if((req.raycastTimer += Time.delta) >= 50f){ for(int i = len - 1; i > req.pathIndex; i--){ int val = items[i]; - //TODO this raycasting is flawed, it assumes units can move through corners even when they can't. if(!raycast(pathType, tileX, tileY, val % wwidth, val / wwidth)){ req.rayPathIndex = i; break; @@ -343,6 +352,7 @@ public class ControlPathfinder implements Runnable{ //total update time no longer than maxUpdate for(var req : threadRequests){ + //TODO this is flawed with many paths req.update(maxUpdate / requests.size); } } @@ -365,6 +375,9 @@ public class ControlPathfinder implements Runnable{ volatile boolean done = false; volatile boolean foundEnd = false; + final Vec2 lastPos = new Vec2(); + float stuckTimer = 0f; + final Vec2 destination = new Vec2(); final Vec2 lastDestination = new Vec2(); @@ -373,7 +386,8 @@ public class ControlPathfinder implements Runnable{ volatile int lastWorldUpdate; //TODO only access on main thread?? - int pathIndex; + volatile int pathIndex; + int rayPathIndex = -1; IntSeq result = new IntSeq(); float raycastTimer; @@ -386,13 +400,11 @@ public class ControlPathfinder implements Runnable{ int start, goal; + long lastUpdateId; long lastTime; volatile int lastId, curId; - //TODO invalidate when not request for a while - long lastUpdateId; - void update(long maxUpdateNs){ if(curId != lastId){ clear(); diff --git a/core/src/mindustry/ai/types/SuicideAI.java b/core/src/mindustry/ai/types/SuicideAI.java index e17cd5d121..dc45290932 100644 --- a/core/src/mindustry/ai/types/SuicideAI.java +++ b/core/src/mindustry/ai/types/SuicideAI.java @@ -19,11 +19,6 @@ public class SuicideAI extends GroundAI{ @Override public void updateUnit(){ - if(disabled()){ - stopShooting(); - return; - } - if(Units.invalidateTarget(target, unit.team, unit.x, unit.y, Float.MAX_VALUE)){ target = null; } diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 38b21406bd..d97cb16b4f 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3144,6 +3144,7 @@ public class Blocks{ coolantOverride = Liquids.water; coolantMultiplier = 6f; + unitFilter = u -> !u.spawnedByCore; shootShake = 1f; ammoPerShot = 5; draw = new DrawTurret("reinforced-"); @@ -3497,7 +3498,7 @@ public class Blocks{ requirements(Category.units, with(Items.graphite, 600, Items.beryllium, 600, Items.oxide, 200, Items.tungsten, 500)); size = 5; //TODO requirements? - plans.add(new AssemblerUnitPlan(UnitTypes.vanquish, 60f * 35f, BlockStack.list(Blocks.tungstenWallLarge, 6, Blocks.duct, 14, Blocks.cliffCrusher, 10))); + plans.add(new AssemblerUnitPlan(UnitTypes.vanquish, 60f * 50f, BlockStack.list(Blocks.tungstenWallLarge, 6, Blocks.duct, 14, Blocks.cliffCrusher, 12))); consumes.power(3f); areaSize = 13; @@ -3508,8 +3509,8 @@ public class Blocks{ shipAssembler = new UnitAssembler("ship-assembler"){{ requirements(Category.units, with(Items.beryllium, 700, Items.oxide, 150, Items.tungsten, 500, Items.silicon, 800)); size = 5; - plans.add(new AssemblerUnitPlan(UnitTypes.quell, 60f * 25f, BlockStack.list(Blocks.berylliumWallLarge, 4, Blocks.duct, 10, Blocks.plasmaBore, 4))); - consumes.power(2f); + plans.add(new AssemblerUnitPlan(UnitTypes.quell, 60f * 40f, BlockStack.list(Blocks.berylliumWallLarge, 4, Blocks.duct, 10, Blocks.plasmaBore, 4))); + consumes.power(3f); areaSize = 13; //consumes.liquid(Liquids.gallium, 2f / 60f); @@ -3520,8 +3521,8 @@ public class Blocks{ mechAssembler = new UnitAssembler("mech-assembler"){{ requirements(Category.units, with(Items.graphite, 600, Items.carbide, 600, Items.oxide, 200, Items.tungsten, 500)); size = 5; - plans.add(new AssemblerUnitPlan(UnitTypes.bulwark, 60f * 40f, BlockStack.list(Blocks.tungstenWallLarge, 5, Blocks.duct, 2))); - consumes.power(2f); + plans.add(new AssemblerUnitPlan(UnitTypes.bulwark, 60f * 60f, BlockStack.list(Blocks.tungstenWallLarge, 5, Blocks.duct, 2))); + consumes.power(3f); areaSize = 13; //consumes.liquid(Liquids.gallium, 2f / 60f); @@ -3605,6 +3606,7 @@ public class Blocks{ constructor = new Constructor("constructor"){{ requirements(Category.units, with(Items.silicon, 100, Items.beryllium, 150, Items.tungsten, 80)); hasPower = true; + buildSpeed = 0.3f; consumes.power(2f); size = 3; }}; @@ -3614,6 +3616,7 @@ public class Blocks{ requirements(Category.units, with(Items.silicon, 150, Items.oxide, 150, Items.tungsten, 200, Items.phaseFabric, 40)); hasPower = true; consumes.power(2f); + buildSpeed = 0.3f; maxBlockSize = 4; minBlockSize = 3; size = 5; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index c115b814b5..2086bf09bb 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -3323,7 +3323,6 @@ public class UnitTypes{ manifold = new ErekirUnitType("manifold"){{ defaultController = CargoAI::new; - defaultAI = true; isCounted = false; allowedInPayloads = false; logicControllable = false; @@ -3350,7 +3349,6 @@ public class UnitTypes{ assemblyDrone = new ErekirUnitType("assembly-drone"){{ defaultController = AssemblerAI::new; - defaultAI = true; flying = true; drag = 0.06f; accel = 0.11f; diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 1bd6163465..7c97aba9d9 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -33,11 +33,6 @@ public class AIController implements UnitController{ @Override public void updateUnit(){ - if(disabled()){ - stopShooting(); - return; - } - //use fallback AI when possible if(useFallback() && (fallback != null || (fallback = fallback()) != null)){ if(fallback.unit != unit) fallback.unit(unit); @@ -57,10 +52,6 @@ public class AIController implements UnitController{ } } - public boolean disabled(){ - return !unit.team.isAI() && !unit.type.defaultAI; - } - @Nullable public AIController fallback(){ return null; diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 55e3485739..b4895d9601 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -69,8 +69,6 @@ public class UnitType extends UnlockableContent{ public boolean logicControllable = true; public boolean playerControllable = true; public boolean allowedInPayloads = true; - /** If false, this unit has no AI when not controlled by a player, regardless of AI controller. */ - public boolean defaultAI = true; /** TODO If true, core units need to "dock" to this unit to work, and can un-dock at the unit instead of respawning at core. */ public boolean coreUnitDock = false; public boolean createWreck = true; @@ -302,10 +300,6 @@ public class UnitType extends UnlockableContent{ } }).growX(); - if(coreUnitDock && !defaultAI){ - table.row().add("@units.nocontroller").growX().left().row(); - } - if(unit.controller() instanceof LogicAI){ table.row(); table.add(Blocks.microProcessor.emoji() + " " + Core.bundle.get("units.processorcontrol")).growX().wrap().left(); diff --git a/core/src/mindustry/type/unit/ErekirUnitType.java b/core/src/mindustry/type/unit/ErekirUnitType.java index 3cfb917d12..099ab679b8 100644 --- a/core/src/mindustry/type/unit/ErekirUnitType.java +++ b/core/src/mindustry/type/unit/ErekirUnitType.java @@ -13,8 +13,6 @@ public class ErekirUnitType extends UnitType{ commandLimit = 0; outlineColor = Pal.darkOutline; envDisabled = Env.space; - //TODO necessary, or not? - defaultAI = false; coreUnitDock = true; unitBasedDefaultController = u -> !playerControllable || u.team.isAI() ? defaultController.get() : new CommandAI(); } diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index 0b1b2d54d7..1c3aaeae30 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -103,6 +103,7 @@ public class Turret extends ReloadTurret{ public boolean playerControllable = true; public boolean displayAmmoMultiplier = true; public Sortf unitSort = UnitSorts.closest; + public Boolf unitFilter = u -> true; public DrawBlock draw = new DrawTurret(); @@ -419,9 +420,9 @@ public class Turret extends ReloadTurret{ float range = range(); if(targetAir && !targetGround){ - target = Units.bestEnemy(team, x, y, range, e -> !e.dead() && !e.isGrounded(), unitSort); + target = Units.bestEnemy(team, x, y, range, e -> !e.dead() && !e.isGrounded() && unitFilter.get(e), unitSort); }else{ - target = Units.bestTarget(team, x, y, range, e -> !e.dead() && (e.isGrounded() || targetAir) && (!e.isGrounded() || targetGround), b -> targetGround, unitSort); + target = Units.bestTarget(team, x, y, range, e -> !e.dead() && unitFilter.get(e) && (e.isGrounded() || targetAir) && (!e.isGrounded() || targetGround), b -> targetGround, unitSort); if(target == null && canHeal()){ target = Units.findAllyTile(team, x, y, range, b -> b.damaged() && b != this);