From 527be41e3299028e2819fe7534ef09c9afbcdaee Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 17 Nov 2020 19:40:18 -0500 Subject: [PATCH] Merged Unit & Builder components / Cleanup --- .../src/main/resources/classids.properties | 5 +++ .../src/main/resources/revisions/alpha/0.json | 1 + .../main/resources/revisions/arkyid/0.json | 1 + .../src/main/resources/revisions/beta/0.json | 1 + .../src/main/resources/revisions/block/4.json | 1 + .../main/resources/revisions/corvus/4.json | 1 + .../src/main/resources/revisions/flare/4.json | 1 + .../src/main/resources/revisions/gamma/0.json | 1 + .../src/main/resources/revisions/mace/4.json | 1 + .../src/main/resources/revisions/mono/3.json | 1 + .../main/resources/revisions/pulsar/0.json | 1 + .../main/resources/revisions/quasar/0.json | 1 + .../src/main/resources/revisions/risso/4.json | 1 + .../main/resources/revisions/toxopid/0.json | 1 + core/assets/bundles/bundle.properties | 1 + core/src/mindustry/ai/types/BuilderAI.java | 29 +++++++------- core/src/mindustry/ai/types/FormationAI.java | 16 ++++---- core/src/mindustry/ai/types/LogicAI.java | 4 +- core/src/mindustry/ai/types/MinerAI.java | 14 +++---- core/src/mindustry/content/UnitTypes.java | 37 ++++++++++-------- core/src/mindustry/core/NetClient.java | 8 ++-- core/src/mindustry/core/NetServer.java | 6 +-- .../mindustry/entities/comp/BuilderComp.java | 19 ++++++--- .../mindustry/entities/comp/BuildingComp.java | 2 +- .../mindustry/entities/comp/MinerComp.java | 6 ++- .../mindustry/entities/comp/PlayerComp.java | 6 +-- .../src/mindustry/entities/comp/UnitComp.java | 6 +-- .../mindustry/graphics/OverlayRenderer.java | 2 +- core/src/mindustry/input/DesktopInput.java | 16 ++++---- core/src/mindustry/input/InputHandler.java | 24 ++++++------ core/src/mindustry/input/MobileInput.java | 14 +++---- core/src/mindustry/logic/LExecutor.java | 31 ++++++--------- core/src/mindustry/type/UnitType.java | 4 +- .../ui/fragments/PlacementFragment.java | 2 +- .../world/blocks/ConstructBlock.java | 2 +- .../world/blocks/payloads/Payload.java | 1 + tests/src/test/java/ApplicationTests.java | 9 +++++ tests/src/test/resources/114.msav | Bin 0 -> 36280 bytes 38 files changed, 152 insertions(+), 125 deletions(-) create mode 100644 annotations/src/main/resources/revisions/alpha/0.json create mode 100644 annotations/src/main/resources/revisions/arkyid/0.json create mode 100644 annotations/src/main/resources/revisions/beta/0.json create mode 100644 annotations/src/main/resources/revisions/block/4.json create mode 100644 annotations/src/main/resources/revisions/corvus/4.json create mode 100644 annotations/src/main/resources/revisions/flare/4.json create mode 100644 annotations/src/main/resources/revisions/gamma/0.json create mode 100644 annotations/src/main/resources/revisions/mace/4.json create mode 100644 annotations/src/main/resources/revisions/mono/3.json create mode 100644 annotations/src/main/resources/revisions/pulsar/0.json create mode 100644 annotations/src/main/resources/revisions/quasar/0.json create mode 100644 annotations/src/main/resources/revisions/risso/4.json create mode 100644 annotations/src/main/resources/revisions/toxopid/0.json create mode 100644 tests/src/test/resources/114.msav diff --git a/annotations/src/main/resources/classids.properties b/annotations/src/main/resources/classids.properties index 43a3517ff0..293373a9df 100644 --- a/annotations/src/main/resources/classids.properties +++ b/annotations/src/main/resources/classids.properties @@ -1,10 +1,13 @@ #Maps entity names to IDs. Autogenerated. alpha=0 +arkyid=29 atrax=1 +beta=30 block=2 corvus=24 flare=3 +gamma=31 mace=4 mega=5 mindustry.entities.comp.BuildingComp=6 @@ -26,6 +29,8 @@ oct=26 poly=18 pulsar=19 quad=23 +quasar=32 risso=20 spiroct=21 +toxopid=33 vela=25 \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/alpha/0.json b/annotations/src/main/resources/revisions/alpha/0.json new file mode 100644 index 0000000000..eff5fa651d --- /dev/null +++ b/annotations/src/main/resources/revisions/alpha/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/arkyid/0.json b/annotations/src/main/resources/revisions/arkyid/0.json new file mode 100644 index 0000000000..eff5fa651d --- /dev/null +++ b/annotations/src/main/resources/revisions/arkyid/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/beta/0.json b/annotations/src/main/resources/revisions/beta/0.json new file mode 100644 index 0000000000..eff5fa651d --- /dev/null +++ b/annotations/src/main/resources/revisions/beta/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/block/4.json b/annotations/src/main/resources/revisions/block/4.json new file mode 100644 index 0000000000..7503c106b6 --- /dev/null +++ b/annotations/src/main/resources/revisions/block/4.json @@ -0,0 +1 @@ +{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/corvus/4.json b/annotations/src/main/resources/revisions/corvus/4.json new file mode 100644 index 0000000000..7503c106b6 --- /dev/null +++ b/annotations/src/main/resources/revisions/corvus/4.json @@ -0,0 +1 @@ +{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/flare/4.json b/annotations/src/main/resources/revisions/flare/4.json new file mode 100644 index 0000000000..7503c106b6 --- /dev/null +++ b/annotations/src/main/resources/revisions/flare/4.json @@ -0,0 +1 @@ +{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/gamma/0.json b/annotations/src/main/resources/revisions/gamma/0.json new file mode 100644 index 0000000000..eff5fa651d --- /dev/null +++ b/annotations/src/main/resources/revisions/gamma/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/mace/4.json b/annotations/src/main/resources/revisions/mace/4.json new file mode 100644 index 0000000000..786a64a7e2 --- /dev/null +++ b/annotations/src/main/resources/revisions/mace/4.json @@ -0,0 +1 @@ +{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/mono/3.json b/annotations/src/main/resources/revisions/mono/3.json new file mode 100644 index 0000000000..364fdca813 --- /dev/null +++ b/annotations/src/main/resources/revisions/mono/3.json @@ -0,0 +1 @@ +{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/pulsar/0.json b/annotations/src/main/resources/revisions/pulsar/0.json new file mode 100644 index 0000000000..b2a9e3161a --- /dev/null +++ b/annotations/src/main/resources/revisions/pulsar/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/quasar/0.json b/annotations/src/main/resources/revisions/quasar/0.json new file mode 100644 index 0000000000..b2a9e3161a --- /dev/null +++ b/annotations/src/main/resources/revisions/quasar/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/risso/4.json b/annotations/src/main/resources/revisions/risso/4.json new file mode 100644 index 0000000000..7503c106b6 --- /dev/null +++ b/annotations/src/main/resources/revisions/risso/4.json @@ -0,0 +1 @@ +{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/annotations/src/main/resources/revisions/toxopid/0.json b/annotations/src/main/resources/revisions/toxopid/0.json new file mode 100644 index 0000000000..eff5fa651d --- /dev/null +++ b/annotations/src/main/resources/revisions/toxopid/0.json @@ -0,0 +1 @@ +{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 06533c4bcd..e20bd856c8 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1279,6 +1279,7 @@ liquid.slag.description = Refined in separators into constituent metals, or spra liquid.oil.description = Used in advanced material production and as incendiary ammunition. liquid.cryofluid.description = Used as coolant in reactors, turrets and factories. +block.resupply-point.description = Resupplies nearby units with copper ammunition. Not compatible with units that require battery power. block.armored-conveyor.description = Moves items forward. Does not accept inputs from the sides. block.illuminator.description = Emits light. block.message.description = Stores a message for communication between allies. diff --git a/core/src/mindustry/ai/types/BuilderAI.java b/core/src/mindustry/ai/types/BuilderAI.java index 76200981ac..acb7464b9d 100644 --- a/core/src/mindustry/ai/types/BuilderAI.java +++ b/core/src/mindustry/ai/types/BuilderAI.java @@ -14,21 +14,20 @@ import static mindustry.Vars.*; public class BuilderAI extends AIController{ float buildRadius = 1500; boolean found = false; - @Nullable Builderc following; + @Nullable Unit following; @Override public void updateMovement(){ - Builderc builder = (Builderc)unit; - if(builder.moving()){ - builder.lookAt(builder.vel().angle()); + if(unit.moving()){ + unit.lookAt(unit.vel.angle()); } if(target != null && shouldShoot()){ unit.lookAt(target); } - builder.updateBuilding(true); + unit.updateBuilding = true; if(following != null){ //try to follow and mimic someone @@ -36,18 +35,18 @@ public class BuilderAI extends AIController{ //validate follower if(!following.isValid() || !following.activelyBuilding()){ following = null; - builder.plans().clear(); + unit.plans.clear(); return; } //set to follower's first build plan, whatever that is - builder.plans().clear(); - builder.plans().addFirst(following.buildPlan()); + unit.plans.clear(); + unit.plans.addFirst(following.buildPlan()); } - if(builder.buildPlan() != null){ + if(unit.buildPlan() != null){ //approach request if building - BuildPlan req = builder.buildPlan(); + BuildPlan req = unit.buildPlan(); boolean valid = (req.tile().build instanceof ConstructBuild && req.tile().bc().cblock == req.block) || @@ -60,7 +59,7 @@ public class BuilderAI extends AIController{ moveTo(req.tile(), buildingRange - 20f); }else{ //discard invalid request - builder.plans().removeFirst(); + unit.plans.removeFirst(); } }else{ @@ -71,8 +70,8 @@ public class BuilderAI extends AIController{ Units.nearby(unit.team, unit.x, unit.y, buildRadius, u -> { if(found) return; - if(u instanceof Builderc b && u != unit && b.activelyBuilding()){ - BuildPlan plan = b.buildPlan(); + if(u.canBuild() && u != unit && u.activelyBuilding()){ + BuildPlan plan = u.buildPlan(); Building build = world.build(plan.x, plan.y); if(build instanceof ConstructBuild cons){ @@ -80,7 +79,7 @@ public class BuilderAI extends AIController{ //make sure you can reach the request in time if(dist / unit.speed() < cons.buildCost * 0.9f){ - following = b; + following = u; found = true; } } @@ -98,7 +97,7 @@ public class BuilderAI extends AIController{ blocks.removeFirst(); }else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid. //add build request. - builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config)); + unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config)); //shift build plan to tail so next unit builds something else. blocks.addLast(blocks.removeFirst()); }else{ diff --git a/core/src/mindustry/ai/types/FormationAI.java b/core/src/mindustry/ai/types/FormationAI.java index 98f4874779..6c6d3c127d 100644 --- a/core/src/mindustry/ai/types/FormationAI.java +++ b/core/src/mindustry/ai/types/FormationAI.java @@ -59,13 +59,13 @@ public class FormationAI extends AIController implements FormationMember{ unit.moveAt(realtarget.sub(unit).limit(speed)); } - if(unit instanceof Minerc mine && leader instanceof Minerc com){ - if(com.mineTile() != null && mine.validMine(com.mineTile())){ - mine.mineTile(com.mineTile()); + if(unit.canMine() && leader.canMine()){ + if(leader.mineTile != null && unit.validMine(leader.mineTile)){ + unit.mineTile(leader.mineTile); CoreBuild core = unit.team.core(); - if(core != null && com.mineTile().drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(com.mineTile().drop())){ + if(core != null && leader.mineTile.drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(leader.mineTile.drop())){ if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){ Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core); @@ -73,13 +73,13 @@ public class FormationAI extends AIController implements FormationMember{ } } }else{ - mine.mineTile(null); + unit.mineTile(null); } } - if(unit instanceof Builderc build && leader instanceof Builderc com && com.activelyBuilding()){ - build.clearBuilding(); - build.addBuild(com.buildPlan()); + if(unit.canBuild() && leader.canBuild() && leader.activelyBuilding()){ + unit.clearBuilding(); + leader.addBuild(unit.buildPlan()); } } diff --git a/core/src/mindustry/ai/types/LogicAI.java b/core/src/mindustry/ai/types/LogicAI.java index 20650efe53..d2f759150b 100644 --- a/core/src/mindustry/ai/types/LogicAI.java +++ b/core/src/mindustry/ai/types/LogicAI.java @@ -92,9 +92,7 @@ public class LogicAI extends AIController{ } } case stop -> { - if(unit instanceof Builderc build){ - build.clearBuilding(); - } + unit.clearBuilding(); } } diff --git a/core/src/mindustry/ai/types/MinerAI.java b/core/src/mindustry/ai/types/MinerAI.java index ae18e8085b..97d8198bb2 100644 --- a/core/src/mindustry/ai/types/MinerAI.java +++ b/core/src/mindustry/ai/types/MinerAI.java @@ -17,21 +17,21 @@ public class MinerAI extends AIController{ protected void updateMovement(){ Building core = unit.closestCore(); - if(!(unit instanceof Minerc miner) || core == null) return; + if(!(unit.canMine()) || core == null) return; - if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type.range)){ - miner.mineTile(null); + if(unit.mineTile != null && !unit.mineTile.within(unit, unit.type.range)){ + unit.mineTile(null); } if(mining){ if(timer.get(timerTarget2, 60 * 4) || targetItem == null){ - targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i)); + targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i)); } //core full of the target item, do nothing if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){ unit.clearItem(); - miner.mineTile(null); + unit.mineTile(null); return; } @@ -47,7 +47,7 @@ public class MinerAI extends AIController{ moveTo(ore, unit.type.range / 2f, 20f); if(unit.within(ore, unit.type.range)){ - miner.mineTile(ore); + unit.mineTile = ore; } if(ore.block() != Blocks.air){ @@ -56,7 +56,7 @@ public class MinerAI extends AIController{ } } }else{ - miner.mineTile(null); + unit.mineTile = null; if(unit.stack.amount == 0){ mining = true; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index b9a0ee1d09..19606cf7f7 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -17,11 +17,13 @@ import static mindustry.Vars.*; public class UnitTypes implements ContentList{ //region definitions + //(the wall of shame - should fix the legacy stuff eventually...) + //mech public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, scepter, reign; - //mech + builder + miner - public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType nova, pulsar, quasar; + //mech + public static @EntityDef(value = {Unitc.class, Mechc.class}, legacy = true) UnitType nova, pulsar, quasar; //mech public static @EntityDef({Unitc.class, Mechc.class}) UnitType vela; @@ -29,29 +31,29 @@ public class UnitTypes implements ContentList{ //legs public static @EntityDef({Unitc.class, Legsc.class}) UnitType corvus, atrax; - //legs + building - public static @EntityDef({Unitc.class, Legsc.class, Builderc.class}) UnitType spiroct, arkyid, toxopid; + //legs + public static @EntityDef(value = {Unitc.class, Legsc.class}, legacy = true) UnitType spiroct, arkyid, toxopid; - //air (no special traits) + //air public static @EntityDef({Unitc.class}) UnitType flare, eclipse, horizon, zenith, antumbra; - //air, legacy mining + //air public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType mono; - //air + building + mining - public static @EntityDef({Unitc.class, Builderc.class}) UnitType poly; + //air + public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType poly; - //air + building + mining + payload - public static @EntityDef({Unitc.class, Builderc.class, Payloadc.class}) UnitType mega; + //air + payload + public static @EntityDef({Unitc.class, Payloadc.class}) UnitType mega; - //air + building + payload - public static @EntityDef(value = {Unitc.class, Builderc.class, Payloadc.class}, legacy = true) UnitType quad; + //air + payload + public static @EntityDef(value = {Unitc.class, Payloadc.class}, legacy = true) UnitType quad; - //air + building + payload - public static @EntityDef({Unitc.class, Builderc.class, Payloadc.class, AmmoDistributec.class}) UnitType oct; + //air + payload + ammo distribution + public static @EntityDef({Unitc.class, Payloadc.class, AmmoDistributec.class}) UnitType oct; - //air + building + mining - public static @EntityDef({Unitc.class, Builderc.class}) UnitType alpha, beta, gamma; + //air + public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType alpha, beta, gamma; //water public static @EntityDef({Unitc.class, WaterMovec.class}) UnitType risso, minke, bryde, sei, omura; @@ -470,7 +472,6 @@ public class UnitTypes implements ContentList{ mineTier = 1; hitSize = 29f; health = 18000f; - buildSpeed = 1.7f; armor = 9f; landShake = 1.5f; rotateSpeed = 1.5f; @@ -700,6 +701,7 @@ public class UnitTypes implements ContentList{ rippleScale = 2f; legSpeed = 0.2f; ammoType = AmmoTypes.power; + buildSpeed = 1f; legSplashDamage = 32; legSplashRange = 30; @@ -802,6 +804,7 @@ public class UnitTypes implements ContentList{ rippleScale = 3f; legSpeed = 0.19f; ammoType = AmmoTypes.powerHigh; + buildSpeed = 1f; legSplashDamage = 80; legSplashRange = 60; diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index 9fff2051e1..f2cc3d6353 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -571,13 +571,13 @@ public class NetClient implements ApplicationListener{ BuildPlan[] requests = null; if(player.isBuilder()){ //limit to 10 to prevent buffer overflows - int usedRequests = Math.min(player.builder().plans().size, 10); + int usedRequests = Math.min(player.unit().plans().size, 10); int totalLength = 0; //prevent buffer overflow by checking config length for(int i = 0; i < usedRequests; i++){ - BuildPlan plan = player.builder().plans().get(i); + BuildPlan plan = player.unit().plans().get(i); if(plan.config instanceof byte[] b){ int length = b.length; totalLength += length; @@ -591,7 +591,7 @@ public class NetClient implements ApplicationListener{ requests = new BuildPlan[usedRequests]; for(int i = 0; i < usedRequests; i++){ - requests[i] = player.builder().plans().get(i); + requests[i] = player.unit().plans().get(i); } } @@ -607,7 +607,7 @@ public class NetClient implements ApplicationListener{ unit.rotation, unit instanceof Mechc m ? m.baseRotation() : 0, unit.vel.x, unit.vel.y, - player.unit().mineTile(), + player.unit().mineTile, player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding, requests, Core.camera.position.x, Core.camera.position.y, diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index a3340f510b..959874e82d 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -600,8 +600,8 @@ public class NetServer implements ApplicationListener{ player.unit().aim(pointerX, pointerY); if(player.isBuilder()){ - player.builder().clearBuilding(); - player.builder().updateBuilding(building); + player.unit().clearBuilding(); + player.unit().updateBuilding(building); if(requests != null){ for(BuildPlan req : requests){ @@ -625,7 +625,7 @@ public class NetServer implements ApplicationListener{ con.rejectedRequests.add(req); continue; } - player.builder().plans().addLast(req); + player.unit().plans().addLast(req); } } } diff --git a/core/src/mindustry/entities/comp/BuilderComp.java b/core/src/mindustry/entities/comp/BuilderComp.java index 4d9f81fa65..12fd9e8029 100644 --- a/core/src/mindustry/entities/comp/BuilderComp.java +++ b/core/src/mindustry/entities/comp/BuilderComp.java @@ -13,6 +13,7 @@ import mindustry.entities.units.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.type.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.blocks.ConstructBlock.*; @@ -22,17 +23,22 @@ import java.util.*; import static mindustry.Vars.*; @Component -abstract class BuilderComp implements Unitc{ +abstract class BuilderComp implements Posc, Teamc, Rotc{ static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()}; @Import float x, y, rotation; + @Import UnitType type; @SyncLocal Queue plans = new Queue<>(1); @SyncLocal transient boolean updateBuilding = true; + public boolean canBuild(){ + return type.buildSpeed > 0; + } + @Override public void update(){ - if(!updateBuilding) return; + if(!updateBuilding || !canBuild()) return; float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange; boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources; @@ -102,9 +108,9 @@ abstract class BuilderComp implements Unitc{ ConstructBuild entity = tile.bc(); if(current.breaking){ - entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier); + entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier); }else{ - entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config); + entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier, current.config); } current.stuck = Mathf.equal(current.progress, entity.progress); @@ -161,6 +167,8 @@ abstract class BuilderComp implements Unitc{ /** Add another build requests to the queue, if it doesn't exist there yet. */ void addBuild(BuildPlan place, boolean tail){ + if(!canBuild()) return; + BuildPlan replace = null; for(BuildPlan request : plans){ if(request.x == place.x && request.y == place.y){ @@ -196,9 +204,8 @@ abstract class BuilderComp implements Unitc{ return plans.size == 0 ? null : plans.first(); } - @Override public void draw(){ - if(!isBuilding() || !updateBuilding) return; + if(!isBuilding() || !updateBuilding || !canBuild()) return; //TODO check correctness Draw.z(Layer.flyingUnit); diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 2dc21c8c66..2f63bd1063 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -1122,7 +1122,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, /** Returns whether or not a hand cursor should be shown over this block. */ public Cursor getCursor(){ - return block.configurable ? SystemCursor.hand : SystemCursor.arrow; + return block.configurable && team == player.team() ? SystemCursor.hand : SystemCursor.arrow; } /** diff --git a/core/src/mindustry/entities/comp/MinerComp.java b/core/src/mindustry/entities/comp/MinerComp.java index 8a55a19c1f..eb5802711e 100644 --- a/core/src/mindustry/entities/comp/MinerComp.java +++ b/core/src/mindustry/entities/comp/MinerComp.java @@ -32,7 +32,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{ } boolean mining(){ - return mineTile != null && !(((Object)this) instanceof Builderc b && b.activelyBuilding()); + return mineTile != null && !this.self().activelyBuilding(); } public boolean validMine(Tile tile, boolean checkDst){ @@ -44,6 +44,10 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{ return validMine(tile, true); } + public boolean canMine(){ + return type.mineSpeed > 0; + } + @Override public void update(){ Building core = closestCore(); diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index bde8712e36..0f8236377c 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -48,7 +48,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra transient float textFadeTime; public boolean isBuilder(){ - return unit instanceof Builderc; + return unit.canBuild(); } public @Nullable CoreBuild closestCore(){ @@ -160,10 +160,6 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra return unit; } - public Builderc builder(){ - return !(unit instanceof Builderc) ? Nulls.builder : (Builderc)unit; - } - public void unit(Unit unit){ if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead."); if(this.unit == unit) return; diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 097cce0130..aa02f25022 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -31,7 +31,7 @@ import mindustry.world.blocks.payloads.*; import static mindustry.Vars.*; @Component(base = true) -abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc{ +abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc, Builderc{ @Import boolean hovering, dead; @Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo, minFormationSpeed; @@ -88,8 +88,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I /** @return where the unit wants to look at. */ public float prefRotation(){ - if(this instanceof Builderc builder && builder.activelyBuilding()){ - return angleTo(builder.buildPlan()); + if(activelyBuilding()){ + return angleTo(buildPlan()); }else if(mineTile() != null){ return angleTo(mineTile()); }else if(moving()){ diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index d2a3cb3700..6723f81c66 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -28,7 +28,7 @@ public class OverlayRenderer{ if(player.dead()) return; if(player.isBuilder()){ - player.builder().drawBuildRequests(); + player.unit().drawBuildRequests(); } input.drawBottom(); diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index 611c9b5305..b35778c881 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -59,7 +59,7 @@ public class DesktopInput extends InputHandler{ group.fill(t -> { t.bottom(); t.visible(() -> { - t.color.a = Mathf.lerpDelta(t.color.a, player.builder().isBuilding() ? 1f : 0f, 0.15f); + t.color.a = Mathf.lerpDelta(t.color.a, player.unit().isBuilding() ? 1f : 0f, 0.15f); return ui.hudfrag.shown && Core.settings.getBool("hints") && selectRequests.isEmpty() && t.color.a > 0.01f; }); @@ -290,7 +290,7 @@ public class DesktopInput extends InputHandler{ cursorType = cursor.build.getCursor(); } - if(isPlacing() || !selectRequests.isEmpty()){ + if((isPlacing() && player.isBuilder()) || !selectRequests.isEmpty()){ cursorType = SystemCursor.hand; } @@ -366,7 +366,7 @@ public class DesktopInput extends InputHandler{ int rawCursorX = World.toTile(Core.input.mouseWorld().x), rawCursorY = World.toTile(Core.input.mouseWorld().y); // automatically pause building if the current build queue is empty - if(Core.settings.getBool("buildautopause") && isBuilding && !player.builder().isBuilding()){ + if(Core.settings.getBool("buildautopause") && isBuilding && !player.unit().isBuilding()){ isBuilding = false; buildWasAutoPaused = true; } @@ -388,7 +388,7 @@ public class DesktopInput extends InputHandler{ } if(Core.input.keyTap(Binding.clear_building)){ - player.builder().clearBuilding(); + player.unit().clearBuilding(); } if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard() && mode != breaking){ @@ -480,8 +480,8 @@ public class DesktopInput extends InputHandler{ deleting = true; }else if(selected != null){ //only begin shooting if there's no cursor event - if(!tileTapped(selected.build) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !player.builder().activelyBuilding() && !droppingItem && - !tryBeginMine(selected) && player.unit().mineTile() == null && !Core.scene.hasKeyboard()){ + if(!tileTapped(selected.build) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !player.unit().activelyBuilding() && !droppingItem && + !tryBeginMine(selected) && player.unit().mineTile == null && !Core.scene.hasKeyboard()){ player.shooting = shouldShoot; } }else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine @@ -506,7 +506,7 @@ public class DesktopInput extends InputHandler{ if(Core.input.keyDown(Binding.select) && mode == none && !isPlacing() && deleting){ BuildPlan req = getRequest(cursorX, cursorY); if(req != null && req.breaking){ - player.builder().plans().remove(req); + player.unit().plans().remove(req); } }else{ deleting = false; @@ -547,7 +547,7 @@ public class DesktopInput extends InputHandler{ if(sreq != null){ if(getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null){ - player.builder().plans().remove(sreq, true); + player.unit().plans().remove(sreq, true); } sreq = null; } diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index bd72655e18..ed219c026e 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -169,7 +169,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ @Remote(variants = Variant.one) public static void removeQueueBlock(int x, int y, boolean breaking){ - player.builder().removeBuild(x, y, breaking); + player.unit().removeBuild(x, y, breaking); } @Remote(targets = Loc.both, called = Loc.server) @@ -383,7 +383,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ public Eachable allRequests(){ return cons -> { - for(BuildPlan request : player.builder().plans()) cons.get(request); + for(BuildPlan request : player.unit().plans()) cons.get(request); for(BuildPlan request : selectRequests) cons.get(request); for(BuildPlan request : lineRequests) cons.get(request); }; @@ -401,7 +401,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ player.typing = ui.chatfrag.shown(); if(player.isBuilder()){ - player.builder().updateBuilding(isBuilding); + player.unit().updateBuilding(isBuilding); } if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){ @@ -653,7 +653,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ return r2.overlaps(r1); }; - for(BuildPlan req : player.builder().plans()){ + for(BuildPlan req : player.unit().plans()){ if(test.get(req)) return req; } @@ -678,7 +678,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ Draw.color(Pal.remove); Lines.stroke(1f); - for(BuildPlan req : player.builder().plans()){ + for(BuildPlan req : player.unit().plans()){ if(req.breaking) continue; if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){ drawBreaking(req); @@ -740,7 +740,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ for(BuildPlan req : requests){ if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){ BuildPlan copy = req.copy(); - player.builder().addBuild(copy); + player.unit().addBuild(copy); } } } @@ -804,7 +804,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ //remove build requests Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize); - Iterator it = player.builder().plans().iterator(); + Iterator it = player.unit().plans().iterator(); while(it.hasNext()){ BuildPlan req = it.next(); if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){ @@ -1044,7 +1044,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } public boolean canShoot(){ - return block == null && !onConfigurable() && !isDroppingItem() && !player.builder().activelyBuilding() && + return block == null && !onConfigurable() && !isDroppingItem() && !player.unit().activelyBuilding() && !(player.unit() instanceof Mechc && player.unit().isFlying()); } @@ -1090,7 +1090,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } public boolean validPlace(int x, int y, Block type, int rotation, BuildPlan ignore){ - for(BuildPlan req : player.builder().plans()){ + for(BuildPlan req : player.unit().plans()){ if(req != ignore && !req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).overlaps(type.bounds(x, y, Tmp.r2)) @@ -1108,15 +1108,15 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ public void placeBlock(int x, int y, Block block, int rotation){ BuildPlan req = getRequest(x, y); if(req != null){ - player.builder().plans().remove(req); + player.unit().plans().remove(req); } - player.builder().addBuild(new BuildPlan(x, y, rotation, block, block.nextConfig())); + player.unit().addBuild(new BuildPlan(x, y, rotation, block, block.nextConfig())); } public void breakBlock(int x, int y){ Tile tile = world.tile(x, y); if(tile != null && tile.build != null) tile = tile.build.tile; - player.builder().addBuild(new BuildPlan(tile.x, tile.y)); + player.unit().addBuild(new BuildPlan(tile.x, tile.y)); } public void drawArrow(Block block, int x, int y, int rotation){ diff --git a/core/src/mindustry/input/MobileInput.java b/core/src/mindustry/input/MobileInput.java index 6edea2d80a..fa39ad5492 100644 --- a/core/src/mindustry/input/MobileInput.java +++ b/core/src/mindustry/input/MobileInput.java @@ -114,7 +114,7 @@ public class MobileInput extends InputHandler implements GestureListener{ } } - for(BuildPlan req : player.builder().plans()){ + for(BuildPlan req : player.unit().plans()){ Tile other = world.tile(req.x, req.y); if(other == null || req.breaking) continue; @@ -219,10 +219,10 @@ public class MobileInput extends InputHandler implements GestureListener{ BuildPlan copy = request.copy(); if(other == null){ - player.builder().addBuild(copy); + player.unit().addBuild(copy); }else if(!other.breaking && other.x == request.x && other.y == request.y && other.block.size == request.block.size){ - player.builder().plans().remove(other); - player.builder().addBuild(copy); + player.unit().plans().remove(other); + player.unit().addBuild(copy); } } @@ -245,10 +245,10 @@ public class MobileInput extends InputHandler implements GestureListener{ Boolp schem = () -> lastSchematic != null && !selectRequests.isEmpty(); group.fill(t -> { - t.visible(() -> (player.builder().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get()); + t.visible(() -> (player.unit().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get()); t.bottom().left(); t.button("@cancel", Icon.cancel, () -> { - player.builder().clearBuilding(); + player.unit().clearBuilding(); selectRequests.clear(); mode = none; block = null; @@ -914,7 +914,7 @@ public class MobileInput extends InputHandler implements GestureListener{ } //update shooting if not building + not mining - if(!player.builder().isBuilding() && player.unit().mineTile() == null){ + if(!player.unit().isBuilding() && player.unit().mineTile == null){ //autofire targeting if(manualShooting){ diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index d4c20eaf5e..244746ef81 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -321,13 +321,8 @@ public class LExecutor{ ((LogicAI)unit.controller()).controller = exec.building(varThis); //clear old state - if(unit instanceof Minerc miner){ - miner.mineTile(null); - } - - if(unit instanceof Builderc builder){ - builder.clearBuilding(); - } + unit.mineTile = null; + unit.clearBuilding(); return (LogicAI)unit.controller(); } @@ -357,12 +352,8 @@ public class LExecutor{ //stop mining/building if(type == LUnitControl.stop){ - if(unit instanceof Minerc miner){ - miner.mineTile(null); - } - if(unit instanceof Builderc build){ - build.clearBuilding(); - } + unit.mineTile = null; + unit.clearBuilding(); } } case within -> { @@ -390,8 +381,8 @@ public class LExecutor{ } case mine -> { Tile tile = world.tileWorld(x1, y1); - if(unit instanceof Minerc miner){ - miner.mineTile(miner.validMine(tile) ? tile : null); + if(unit.canMine()){ + unit.mineTile = unit.validMine(tile) ? tile : null; } } case payDrop -> { @@ -432,12 +423,12 @@ public class LExecutor{ } } case build -> { - if(unit instanceof Builderc builder && exec.obj(p3) instanceof Block block){ + if(unit.canBuild() && exec.obj(p3) instanceof Block block){ int x = World.toTile(x1), y = World.toTile(y1); int rot = exec.numi(p4); //reset state of last request when necessary - if(ai.plan.x != x || ai.plan.y != y || ai.plan.block != block || builder.plans().isEmpty()){ + if(ai.plan.x != x || ai.plan.y != y || ai.plan.block != block || unit.plans.isEmpty()){ ai.plan.progress = 0; ai.plan.initialized = false; ai.plan.stuck = false; @@ -446,11 +437,11 @@ public class LExecutor{ ai.plan.set(x, y, rot, block); ai.plan.config = exec.obj(p5) instanceof Content c ? c : null; - builder.clearBuilding(); + unit.clearBuilding(); if(ai.plan.tile() != null){ - builder.updateBuilding(true); - builder.addBuild(ai.plan); + unit.updateBuilding = true; + unit.addBuild(ai.plan); } } } diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 2a5be98056..6da3a75ec0 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -80,7 +80,7 @@ public class UnitType extends UnlockableContent{ public int ammoCapacity = -1; public AmmoType ammoType = AmmoTypes.copper; public int mineTier = -1; - public float buildSpeed = 1f, mineSpeed = 1f; + public float buildSpeed = -1f, mineSpeed = 1f; public Sound mineSound = Sounds.minebeam; public float mineSoundVolume = 0.6f; @@ -225,7 +225,7 @@ public class UnitType extends UnlockableContent{ stats.addPercent(Stat.mineSpeed, mineSpeed); stats.add(Stat.mineTier, new BlockFilterValue(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= mineTier && !f.playerUnmineable)); } - if(inst instanceof Builderc){ + if(buildSpeed > 0){ stats.addPercent(Stat.buildSpeed, buildSpeed); } if(inst instanceof Payloadc){ diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java index 4c268e5dcd..9115b16785 100644 --- a/core/src/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/mindustry/ui/fragments/PlacementFragment.java @@ -102,7 +102,7 @@ public class PlacementFragment extends Fragment{ Block tryRecipe = tile == null ? null : tile.block instanceof ConstructBlock ? ((ConstructBuild)tile).cblock : tile.block; Object tryConfig = tile == null ? null : tile.config(); - for(BuildPlan req : player.builder().plans()){ + for(BuildPlan req : player.unit().plans()){ if(!req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).contains(Core.input.mouseWorld())){ tryRecipe = req.block; tryConfig = req.config; diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java index ce37e80dde..34532df8b0 100644 --- a/core/src/mindustry/world/blocks/ConstructBlock.java +++ b/core/src/mindustry/world/blocks/ConstructBlock.java @@ -179,7 +179,7 @@ public class ConstructBlock extends Block{ if(control.input.buildWasAutoPaused && !control.input.isBuilding && player.isBuilder()){ control.input.isBuilding = true; } - player.builder().addBuild(new BuildPlan(tile.x, tile.y, rotation, cblock, lastConfig), false); + player.unit().addBuild(new BuildPlan(tile.x, tile.y, rotation, cblock, lastConfig), false); } } diff --git a/core/src/mindustry/world/blocks/payloads/Payload.java b/core/src/mindustry/world/blocks/payloads/Payload.java index 0e4773538f..ec174f238d 100644 --- a/core/src/mindustry/world/blocks/payloads/Payload.java +++ b/core/src/mindustry/world/blocks/payloads/Payload.java @@ -57,6 +57,7 @@ public interface Payload{ return (T)payload; }else if(type == payloadUnit){ byte id = read.b(); + if(EntityMapping.map(id) == null) throw new RuntimeException("No type with ID " + id + " found."); Unit unit = (Unit)EntityMapping.map(id).get(); unit.read(read); return (T)new UnitPayload(unit); diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 72393306ad..5895b7fe10 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -433,6 +433,15 @@ public class ApplicationTests{ assertEquals(256, world.height()); } + @Test + void load114Save(){ + resetWorld(); + SaveIO.load(Core.files.internal("114.msav")); + + assertEquals(500, world.width()); + assertEquals(500, world.height()); + } + @Test void arrayIterators(){ Seq arr = Seq.with("a", "b" , "c", "d", "e", "f"); diff --git a/tests/src/test/resources/114.msav b/tests/src/test/resources/114.msav new file mode 100644 index 0000000000000000000000000000000000000000..e0c9277c7713517ee79dff4994bb85d7fd2a6131 GIT binary patch literal 36280 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
  • l9(c5(vU$d;9DrxM#r(TH;Fgi%jimEV z(y5Hzx_{do^g)(H$1UAKBl{ggdYBv`U$qzN?X?_kd5W3q>fmu9*6%3kI>Nc$K>AFs zZ0}S?sGb}>itcS>zoV3mJ0X9T1gD4etb}Ji6`U@3Bfb@Wb&;;Y92`YM9VB0FA-u%_p0b{=;M`Vmo|JK=hH&h2Wqa=kvVV|m zZh}Yli{}w`j7V)JY#9-fEO=&IWyU(ly*@>bmA4vR4&S$`h*vJ5^?_FrzbYi9NH@o; z>^BDyE0UDQt86aUVXRmQLh_}mQp$3oxFuyxvpKvyxUJToojG|Q!5X9JKfFAyZS$Vy z(%Ki-1e#JF4$p6-+j32ytt<35Uc3gXZ!@>Q%EmLGO*I$uv?)-jJ4cgtyoxYCw<4Vm zn3=cJ(=>0fIV+E&0!NYzt$wGEBT;q6Zg@7}R*s}TH;OY5^7%8q{A8r(Pnz6i9KuSj zPkTBjX?1m&j2?IqAthNiGWWAz=NarrYtE7WI?}JAuyvdQT`f9t^ed@ERS+iG>Fwu= zvlzqjIX+JXCeBKt^t!d*$}6;d9>da}Wq*5qh-8H@SpDa=CgSK8&vVqLo54zW%F2=4 zP&dL;&ugd!sI;UH)zd`i@$N2m3(jCBEZeqrR_F3uOUSvAtO}m9SGmC>M^qa`US`8NP85pp=0Ev; zXV5l3{L^m{o`bh#X13G#sz|f)H7QqZGsjoU@eSq1r<`EJ=R7`Ha?YE?#G$rwDy5Ly z3Qzl94R>E03`1SSp-vV>;k-#%>-igHD)u&i<|~<>R8>k4B<|RcisFbn@{ARCssne% zi-I&?8vYdj(zbLaXQ!@34e-S0ZCoJs_XY4Gd*X>yOXA5ww%vfzq&nr#Xa4UT zh1W*fGqd%aH;}2qsi$nX4Xy)HmPmQiD-xrnxcjB!c6~USn`id4^Uj4oO_#_RTcy1@ zUe8DJmD1cK`Ut$Fh)R?z)t<^M$5f1K;HfWdYhU*#TCwvT>ltj0V#P|z*WK_c<;DVK zZXTt)DH2H8>Qp#`+sEneg6j&m^O~tFG_{;U-Q}YG?riW$hk6h1 zf;)&$I{P|!RxuNqJD`mIXE9BsD3t`$__a5nZ-OU~lG$xJ&$0{8aawxIkbgQhwZW6_ z$Kta_@9sdiZBJ5#O3&4H9%$}^{I1W8=4J5gSEAWOXjh=Q6=O^`xwiP8?a0ym|7`nA z4C&!na8@Ba<@uw8uPtHzYRRVp-}_CxK=&h=g3ia>cJnS#J}|2M{5Es0lx9mYDB8_s zBGnesQB?G$b!GGqod^0;@yn#kIUn>(Ici(665?$qj}YiLz6~Q4Ug!w-*<|`SE0Jg+ zO0%vlf?I;5L$y;vF@9rE4K=r7N0OvKC`Z!iSR>2yAYrQ3RD7om!gB`$Uh|_V;O*rU z-wA)qiXNM;`Cl}=&WO3R^}y>ZCb3nX;dFA1L3Pr`idJBw<1To;LhGkmCv|5xU3$73 zNnVoCjb6$-u#Ii%;H9v7xq9!0mk6zZr}&@0b;Cn4Jq@Fm)N3IP_Jke*Xk8d%)Roh5 z54>ckq`dwN>9CcukYb}!5~8D-C$8zX)^Nw~XWM(=hH;h1J}WJYlQ6(3X@_S)RkcC8 zyDJjgeq^6cHd?Q^*NC~S5{Wf~@QkbNaK8^#+Uh~lhi?i-ok6aWqi{{WvboNM%is;b zv#$zQA3P(w;j*jrW=gy=i64Wi3VL#Y7K-N}yi&Aat`D%bz_UMg6=9{-)&$R9tDRsm zR3*ycnAK$)r*<*Yz4#dPM3@b0A?I*OaR*BZ`3TG4FXh}Gh1+5q;gx%a$?xSzS|(Yv zRB3Dh#c?&fI+Vd%K|UW1H0EJn0{1zIhCwaees~ooRVPzs`$7)3GUm3iO@v)R*fs|W zpi`x1`2xwfxSq5fg)B|-Z77A^0?(;Y3-{0HfL9aiCeF?57h$TZK!n-EZOw2g!d#h_ z^~T=MZQv-BVcGqg+5;@z`<1m<)uL=p4v^lX(7P(FfvN!GQo>hCz@Y%czQ2ZZ`9!gO z%1pb6GSk5|Yh%2QJU>a^T3g#ghT)Z;PO!~~*yb{%l&z!i>W?SLo5$JaawLDdCFG5K zu^y#-==-G{xB5R4zF$Y#F0j9S|3S9b_p8ZseJ>e$7+%R^9lT1@U%~yFj5lwBSGZ6W zvX;<(jB<22yqcV~e*@*{3V4-BJ@u{sR?=}P-1klIzXhS6Qsb^6-?qTB8`w>x7oLe( z7uY)~JF1|eeOxfM!c+gqm#qta{2n0a=mq$PcKi>z>Tp|1>YvKObCKLB^ZeIac+K&>R(IY=C(Ivl0eJ*(Q0iLbb>$s*;1&VYvSJi2qp$p*E zh#KJDD}q@^m^N11M-GOu8myFVe5~m2E#*T~j=loh3YP7W*92I@NauqpN!U*>{4uS) zLA1#i)#hP+k-tf%V%4t&0f(M1(a%y<0g{Q_v)~Iy4d-klwBD1WmW^E%!g_-ojj)e! zRKV{%_3&493zW{*D@ARDzaLB~H$)uCu_F0oS0ed9*T8KK@`F}YgH#dN*Wz0KuH~3E za(?V*7kR${?qoI=@YJHTTD|U@HINH|??G$%yAEC*SXEtHh)dO=tKfdnHox1+Klyiy zf4Z9u0mc1V=U_vCZEJoB`8EPSx3w?ZU0_GRQIFX+n?*L8NMu&-gzr~2&b>jd8w1V-l+`?Mn{qf?I8JF!YPgq4-Wmc<(QRgItJt`l@+jD` zQn*3kriR5&$fi`TIPVSH=rsWHc~EYp!bgU?UZn5#|#~* z&!+I5;A=g#a6VE6&9S26r_V#$qbdW97eUpQ(`OqvC!y1*ZMh>v38G9wb`DTy(-~e&EE^*e%KzvkopbI zla7iYXQ!g0pXI~&vHKQg4d+hE!i-~2rJ zbq~BqkNOxtE(?9^M7JnN3)fb?@7B=1ne8=d&PScF@q5Or- z#D6b8R}8^pW%>^hzxJd)xMuh`>3bAP9OUa`g#A=gwU6>tzF+BWM4DCG6Q$|AkGy*X zif~Nxv4rzu^HRY+(k|T3b*o6=4-!`FyN|q&eT_G7;oN=z{(4(VnG;M!=GpfTvtPdb zWgKf4zgN5W^LI7d?I*nM(5{v3+Q@sg>xUb@-op0p;}~rmc3#M`u#)Y&L;F^?mp`gS zUMcEvFtl$c9mccwhW2^^*nBKtd;M0F>{~fc?}fiqEt;&4Yo|Z7H$0n>&b3%ix1mgb z8Swfb&1G8Kdfkq|!vhSCv`71q_9BC`iu@afM}12JriRcCH98Dgv)ND7(Em zSZgTX$ztSo>l@)Ojwbj%DU-TnqLuJ_*|#simbQBVz>yUWO4wiF`2%zJ!Ct)=Hp(ju?Q>jL&@V-z@y+54`5jy zwnAxK)BGBa?VPI!b7_#FHp=8G@=!c0u$_IriT#Y$tvPrtY}XL_*;#A}&YI!(Ex9rY zp7k>q<}L8-x&81|QSuSd9dQj@>yU@HneBEEUV9GSDBPz*+PJkS)z)-`@y1A#isIh@ z;M%R8;y#jOPW!ykkzUlwa*#fkroI!N=`6-?D2iEHydkJ2t|yRA*2e^I7fLlHg0K5* zAqU@!S$N9T0+i9htpLgXNKGR&>pZpyq zxJ?0WZ*g4CkClOL!Bw4HgR={+84`cM^(MMC*8`M;#e~G{EpIG zZ139bwxFx%BOJkR%h9gX$0?HE(|Zd~42%WAJXSU+Kw!?_HK0+x;<(V(QNdeg@-Hs{8uIP;Wg z#^t-6{MeOSFH7Lw>(x+%p)4b%xSDd~)0^IYYaAM1TMn0eIU9NBx|#ov@Egj&S1E`X zr`}Kc+Mv6R;~0Ub+r9~DkE;qe>T@{i;hsU&@ViVe{7n>b$P#4t6op4Coo^wBOK$QJ zR}Z}4sqP;=NcW32c&JkvkKa!#xmGLT9w$1BBM)nGI;CpI(`&?OYbcP9cBRr&g zkUWp|cAFt@pVV$Y;Z>pZj$KW87C$@TVQ-C;#W>aik_AS(3gsslLGl2JxGHmF6mH3f za5V*73S&^XcYoNQR&l%|NOyvW3%PMsz%3ODSyR@Na(Mc}T9Y@O;pq;1GFm4%*Q?=o zU;ssHg>bb5T-yV#6#MN~ecg|QOCEnF)7ugCyC_;*B97)caOf^R8h9{*)!M2Gp9^+H@&9g-6>QbhZXrEe0?l%|wJ{AK?MKpOk6Ow~_7|c;+g>9z?P__rbHP zHDj#k{b}Q$3~=>M>j?5J*@|%OZl-rfBRqbqu22SVF|W8O=4(Q9oKI+{QWQh_hxE@zbj?d%fr3|zNbq^Z2VLJKrdnHJWQpZTgv=Y}W?A&q&XwbE7f9jP$N1%w_Owj(OPW&YkXtdRqy013X!|V2fT` z|YabDQLMKrMd>` zo);sXMcRux;rhT~py-`7HUrx|)8rwR$5{cl`q!b<<2~Zk-9~pndA(GVSK6l+a{Vud zTk;nXXLVSY!kPAV_bOOHIU3GjIWvfbeFh?e7b=1X>eC^9yf)4HV?bt@33R+w}buqI8SXzUQe1w>+#E-{#OVfYuVX>SM|at*1DbrErXY89XE8gY2`N>!UiX7uf(*FHpL>)d5Z? z$M6u+zP$sk^X_DSc~_pQzv?QUMCskB4PL!V{nv0_rr=(h%GsCBi^SWt9Cv$wqlhj= zxYdVsu5w#F0d6}=V>r$3y@7Mt0rv`1be24?lb*$GkGdbodsVt6*&<$tqzA6ajXT2a zgmfxy&(Ymg*^85XUAg!YE#^0J1w6a1GGsQv^>)$+jj~;PfY}1QUs4~*s@**<<@&I` z2jCu%(la)Y2G3xd_ObRH4)IGqL&h)JdhL6&onK4*!#Vi6t3~+sx9%j;W9Y72Lq0Er zd(E1y^D4OCPXpyv(RQl760_+J!}pPhNAE~m2hz_)_;p+lqeb{>jn=^E(hE0IJO5JLi8ecne7a+xj4|> z8DL9CxgK8V$6znx_;+HgIL^j^qdMSdIt7kodq_Aoz&-0PA-|1oI}hv)F8gxK7N`Ae*XN9fidMcU|$i@2iBK32HONm;=~PsT(swAoTlBBcTe1^w-`oI2Q+NccaO^~ik`fNxla)GG_i5>J zI|J<-%FKmqDRw_H|3ukD^uv1SKv_|kMzps?{E|02v=hf zj_Y-mhe5^eI^g=`!`o-y_7+gK*nbH+fkN z&**LQvI%aBlc#)d;J549bC*Clb-2;!Bly9i)|I%NmjB(Q(u#HxuFX?%GKF0}r3Fq2mPVWWT$RtAv zw(~?0T-vMiuqPnk!^xJPc(^kSGhz0@Jw`P812feum1lXMk&Id62cffX~*X_RmiCUj_M-J3igLq%RtP+Z+=< z=a12}EUY8xv|^gO8hF`mz4s08P4K&0EnKf98(ee1Cwb^BgHLZVRegzgR_5^3aa@Y# z7arMe<$JS??X^GY-739!eGbQs@cW|1(jCTR^1GeuHxFACTcrP%KE4S_9};2LLAF8N zQ9WkIWPB;y9^MVN&6R_#>>G8&p}DVT-v!VeKJj!p+`iqK!=(x@%Ig(znSA%bOE%vW z){byVei04T$3$jPmn#ilemoLLuGI%|Nla`J|+1vB^=Tf6o|`Y>sq+JtibUs zBaZ9YF5=KzZxP(4Ciseb(wS)UeGSsy7T>mle<{~wp$s0iU&OT?c~uAZDOP5RtB~$g zIv>O7}=oK)9&+WP)2e8}595rU9p^_?%wz}@?cX%;bX? z9xj29O+7R>+z0nOKa1YV?1f9VX1{)EL+Kpk`Ebs2P>xqsGrX$$r^vuZZXQo>*TvBR^N(VD%u|oUS(uu$7hn3`^s&*B`1ySwd? zc4l9H)jv+vJ$Nt)-1F)4(m7Z)_gLomslZV7nxg=v`@U$czx+7)BDHXxBt4f0t>RQe&F`( zMALW9`!@2_6W`9Beb4RZU0R-c{Fm9&9e=rv}GQ&-lqZN89tToA*BO z;~!OEF#D&EKa-vK>AmM&AfI~d?(7pkdjk8XNw+O!W?LyCT}Q0%i+hLOI#DViMM4*5 zU%db2?5}V9%-P*9CO-a=AI*}zLiUAUeJgwV(3j2<=BM8EWEq%Wd}Cwwv2Wr30DCBR E@66-QcK`qY literal 0 HcmV?d00001