diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index b87e0dd5b9..034e52e9d1 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1312,7 +1312,7 @@ bar.nevermelts = never melts bar.heatamount = Heat: {0} bar.heatpercent = Heat: {0} ({1}%) bar.power = Power -bar.progress = Build Progress +bar.progress = Progress: {0}% bar.loadprogress = Progress bar.launchcooldown = Launch Cooldown bar.input = Input diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 3775a24c93..e4d95a7d25 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -17,12 +17,13 @@ import static mindustry.Vars.*; public class CommandAI extends AIController{ protected static final int maxCommandQueueSize = 50, avoidInterval = 10; - protected static final Vec2 vecOut = new Vec2(), vecMovePos = new Vec2(); + protected static final Vec2 vecOut = new Vec2(), vecMovePos = new Vec2(), movePos = new Vec2(); protected static final UnitPayload tmpPayload = new UnitPayload(null); protected static final int transferStateNone = 0, transferStateLoad = 1, transferStateUnload = 2; public Seq commandQueue = new Seq<>(5); public @Nullable Vec2 targetPos; + public @Nullable Building targetBuild; public @Nullable Teamc attackTarget; /** Group of units that were all commanded to reach the same point. */ public @Nullable UnitGroup group; @@ -227,14 +228,14 @@ public class CommandAI extends AIController{ } } - if(!net.client() && command == UnitCommand.enterPayloadCommand && unit.type.allowedInPayloads && unit.buildOn() != null && - (targetPos == null || (world.buildWorld(targetPos.x, targetPos.y) != null && world.buildWorld(targetPos.x, targetPos.y) == unit.buildOn()))){ - - var build = unit.buildOn(); - tmpPayload.unit = unit; - if(build.team == unit.team && build.acceptPayload(build, tmpPayload)){ - Call.unitEnteredPayload(unit, build); - return; //no use updating after this, the unit is gone! + if(!net.client() && command == UnitCommand.enterPayloadCommand && unit.type.allowedInPayloads && (targetPos == null ? unit.buildOn() != null : targetBuild != null)){ + var build = targetPos == null ? unit.buildOn() : targetBuild; + if(build.canControlSelect(unit)){ + tmpPayload.unit = unit; + if(build.team == unit.team && build.acceptPayload(build, tmpPayload)){ + Call.unitEnteredPayload(unit, build); + return; //no use updating after this, the unit is gone! + } } } @@ -275,8 +276,19 @@ public class CommandAI extends AIController{ if(targetPos != null){ boolean move = true, isFinalPoint = commandQueue.size == 0; - vecOut.set(targetPos); - vecMovePos.set(targetPos); + //workaround to let ground units pathfind into targeted solid payenter blocks + boolean isSolidPayload = false; + movePos.set(targetPos); + targetBuild = world.buildWorld(targetPos.x, targetPos.y); + if(unit.isGrounded() && targetBuild != null && targetBuild.tile.solid() && targetBuild.block.acceptsUnitPayloads && unit.type.pathCostId != ControlPathfinder.costIdLegs && !ramming){ + Tile best = targetBuild.findClosestEdge(unit, Tile::solid); + if(best != null){ + movePos.set(best); + isSolidPayload = true; + } + } + vecOut.set(movePos); + vecMovePos.set(movePos); //the enter payload command requires an exact position if(group != null && group.valid && groupIndex < group.units.size && command != UnitCommand.enterPayloadCommand){ @@ -287,7 +299,7 @@ public class CommandAI extends AIController{ if( (hasStance(UnitStance.patrol) && !hasStance(UnitStance.pursueTarget) && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget) || - (command == UnitCommand.enterPayloadCommand && unit.within(targetPos, 4f) || (targetBuild != null && !unit.type.circleTarget && unit.within(targetBuild, targetBuild.block.size * tilesize/2f * 0.9f))) || + (command == UnitCommand.enterPayloadCommand && unit.within(movePos, 4f) || (targetBuild != null && !unit.type.circleTarget && unit.within(targetBuild, targetBuild.block.size * tilesize/2f * 0.9f))) || (command == UnitCommand.loopPayloadCommand && unit.within(vecMovePos, 10f)) ){ move = false; @@ -329,9 +341,10 @@ public class CommandAI extends AIController{ move = true; vecOut.set(vecMovePos); }else{ - var result = controlPath.getPathPosition(unit, vecMovePos, targetPos); + var result = controlPath.getPathPosition(unit, movePos, isSolidPayload ? movePos : targetPos); - unreachable = result.unreachable; + if(!isSolidPayload) + unreachable = result.unreachable; move &= result.move && (!blockingUnit || timeSpentBlocked > maxBlockTime); if(result.move) vecOut.set(result.dest); diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 926a73d278..e85afd2a05 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -813,6 +813,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, } public void handlePayload(Building source, Payload payload){ + handlePayload(source, payload, null); + } + + public void handlePayload(Building source, Payload payload, @Nullable Unit unit){ } diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index e2187aaf03..fd9e1edaf4 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -669,7 +669,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ if(build.acceptPayload(build, unitPay)){ Fx.unitDrop.at(build); - build.handlePayload(build, unitPay); + build.handlePayload(build, unitPay, unit); } } diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index a8dc17708f..7dc587f7d5 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -59,7 +59,7 @@ public class Block extends UnlockableContent implements Senseable{ public boolean conductivePower = false; /** If true, this block can output payloads; affects blending. */ public boolean outputsPayload = false; - /** If true, this block can input payloads; affects unit payload enter behavior. */ + /** If true, this block can input payloads. Affects unit payload enter and pathfinding behaviors. */ public boolean acceptsUnitPayloads = false; /** If true, payloads will attempt to move into this block. */ public boolean acceptsPayload = false; @@ -114,6 +114,8 @@ public class Block extends UnlockableContent implements Senseable{ public boolean solidifes; /** if true, this counts as a non-solid block to this team. */ public boolean teamPassable; + /** if true, units will attempt to pathfind to this block even if its tile is normally impassable. */ + public boolean pathfindTarget = false; /** if true, this block cannot be hit by bullets unless explicitly targeted. */ public boolean underBullets; /** whether this is rotatable */ diff --git a/core/src/mindustry/world/blocks/payloads/BlockProducer.java b/core/src/mindustry/world/blocks/payloads/BlockProducer.java index 21136e906b..02d90fdb98 100644 --- a/core/src/mindustry/world/blocks/payloads/BlockProducer.java +++ b/core/src/mindustry/world/blocks/payloads/BlockProducer.java @@ -1,5 +1,6 @@ package mindustry.world.blocks.payloads; +import arc.*; import arc.graphics.g2d.*; import arc.math.*; import arc.struct.*; @@ -71,7 +72,11 @@ public abstract class BlockProducer extends PayloadBlock{ public void setBars(){ super.setBars(); - addBar("progress", (BlockProducerBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe() == null ? 0f : (entity.progress / entity.recipe().buildTime))); + addBar("progress", (BlockProducerBuild e) -> new Bar( + () -> Core.bundle.format("bar.progress", Strings.autoFixed(e.fraction() * 100f, 0)), + () -> Pal.ammo, + e::fraction + )); } public abstract class BlockProducerBuild extends PayloadBlockBuild{ @@ -79,6 +84,10 @@ public abstract class BlockProducer extends PayloadBlock{ public abstract @Nullable Block recipe(); + public float fraction(){ + return recipe() == null ? 0f : (progress / recipe().buildTime); + } + @Override public boolean acceptItem(Building source, Item item){ return items.get(item) < getMaximumAccepted(item); diff --git a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java index 491a8077dc..c91ff926b8 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java @@ -15,7 +15,7 @@ import mindustry.world.meta.*; import static mindustry.Vars.*; public class PayloadBlock extends Block{ - public float payloadSpeed = 0.7f, payloadRotateSpeed = 5f; + public float payloadSpeed = 0.7f, payloadRotateSpeed = 3f; public String regionSuffix = ""; public TextureRegion topRegion, outRegion, inRegion; @@ -101,7 +101,8 @@ public class PayloadBlock extends Block{ @Override public boolean canControlSelect(Unit unit){ - return !unit.spawnedByCore && unit.type.allowedInPayloads && this.payload == null && acceptUnitPayload(unit) && unit.tileOn() != null && unit.tileOn().build == this; + return !unit.spawnedByCore && unit.type.allowedInPayloads && this.payload == null && acceptUnitPayload(unit) && + (unit.isGrounded() && !unit.type.allowLegStep && solid) ? unit.within(this, size * tilesize * 0.7f + unit.hitSize / 2f) : unit.tileOn() != null && unit.tileOn().build == this; } @Override @@ -117,10 +118,15 @@ public class PayloadBlock extends Block{ return this.payload == null; } - @Override - public void handlePayload(Building source, Payload payload){ + public void handlePayload(Building source, Payload payload, @Nullable Unit unit){ + if(unit != null){ + float clampPos = size * tilesize * 0.7f + unit.hitSize / 2f; + this.payVector.set(unit.x, unit.y).sub(this).clamp(-clampPos, -clampPos, clampPos, clampPos); + }else{ + this.payVector.set(source).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f); + } + this.payload = (T)payload; - this.payVector.set(source).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f); this.payRotation = payload.rotation(); updatePayload(); diff --git a/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java b/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java index 8b151af5dc..abdc6db2db 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java @@ -1,5 +1,6 @@ package mindustry.world.blocks.payloads; +import arc.*; import arc.graphics.g2d.*; import arc.math.*; import arc.util.*; @@ -45,7 +46,11 @@ public class PayloadDeconstructor extends PayloadBlock{ public void setBars(){ super.setBars(); - addBar("progress", (PayloadDeconstructorBuild e) -> new Bar("bar.progress", Pal.ammo, () -> e.progress)); + addBar("progress", (PayloadDeconstructorBuild e) -> new Bar( + () -> Core.bundle.format("bar.progress", Strings.autoFixed(e.progress * 100f, 0)), + () -> Pal.ammo, + () -> e.progress + )); } public class PayloadDeconstructorBuild extends PayloadBlockBuild{ diff --git a/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java b/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java index 109c209f31..a9029f092f 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java @@ -146,6 +146,11 @@ public class PayloadMassDriver extends PayloadBlock{ return waitingShooters.isEmpty() ? null : waitingShooters.first(); } + @Override + public boolean acceptUnitPayload(Unit unit){ + return unit != null && unit.hitSize <= maxPayloadSize * tilesize; + } + @Override public float buildRotation(){ return rotation; diff --git a/core/src/mindustry/world/blocks/units/Reconstructor.java b/core/src/mindustry/world/blocks/units/Reconstructor.java index af9f6c8b67..f02fcf384c 100644 --- a/core/src/mindustry/world/blocks/units/Reconstructor.java +++ b/core/src/mindustry/world/blocks/units/Reconstructor.java @@ -68,7 +68,12 @@ public class Reconstructor extends UnitBlock{ public void setBars(){ super.setBars(); - addBar("progress", (ReconstructorBuild entity) -> new Bar("bar.progress", Pal.ammo, entity::fraction)); + addBar("progress", (ReconstructorBuild e) -> new Bar( + () -> Core.bundle.format("bar.progress", Strings.autoFixed(e.fraction() * 100f, 0)), + () -> Pal.ammo, + e::fraction + )); + addBar("units", (ReconstructorBuild e) -> new Bar( () -> e.unit() == null ? "[lightgray]" + Iconc.cancel : @@ -177,6 +182,12 @@ public class Reconstructor extends UnitBlock{ @Override public void onCommand(Vec2 target){ commandPos = target; + if(command != null && command.snapToBuilding){ + var build = world.buildWorld(target.x, target.y); + if(build != null && build.team == this.team){ + commandPos.set(build); + } + } } @Override @@ -186,7 +197,7 @@ public class Reconstructor extends UnitBlock{ public boolean canSetCommand(){ var output = unit(); - return output != null && output.commands.size > 1 && output.allowChangeCommands; + return output == null || output.allowChangeCommands; } @Override @@ -203,25 +214,20 @@ public class Reconstructor extends UnitBlock{ public void buildConfiguration(Table table){ var unit = unit(); - if(unit == null){ - deselect(); - return; - } - var group = new ButtonGroup(); group.setMinCheckCount(0); - int i = 0, columns = 4; + int i = 0, columns = 5; table.background(Styles.black6); - var list = unit().commands; + var list = unit == null ? Vars.content.unitCommands().copy() : unit().commands; for(var item : list){ - ImageButton button = table.button(item.getIcon(), Styles.clearNoneTogglei, 40f, () -> { + ImageButton button = table.button(item.getIcon(), Styles.clearNoneTogglei, 44f, () -> { configure(item); deselect(); }).tooltip(item.localized()).group(group).get(); - button.update(() -> button.setChecked(command == item || (command == null && unit.defaultCommand == item))); + button.update(() -> button.setChecked(command == item || (command == null && unit != null && unit.defaultCommand == item))); if(++i % columns == 0){ table.row(); diff --git a/core/src/mindustry/world/blocks/units/UnitAssembler.java b/core/src/mindustry/world/blocks/units/UnitAssembler.java index 2f883db497..1e7b517766 100644 --- a/core/src/mindustry/world/blocks/units/UnitAssembler.java +++ b/core/src/mindustry/world/blocks/units/UnitAssembler.java @@ -125,7 +125,11 @@ public class UnitAssembler extends PayloadBlock{ removeBar("liquid"); } - addBar("progress", (UnitAssemblerBuild e) -> new Bar("bar.progress", Pal.ammo, () -> e.progress)); + addBar("progress", (UnitAssemblerBuild e) -> new Bar( + () -> Core.bundle.format("bar.progress", Strings.autoFixed(e.progress * 100f, 0)), + () -> Pal.ammo, + () -> e.progress + )); addBar("units", (UnitAssemblerBuild e) -> new Bar(() -> diff --git a/core/src/mindustry/world/blocks/units/UnitFactory.java b/core/src/mindustry/world/blocks/units/UnitFactory.java index 3d39feb336..a2dd96845f 100644 --- a/core/src/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/mindustry/world/blocks/units/UnitFactory.java @@ -118,7 +118,11 @@ public class UnitFactory extends UnitBlock{ @Override public void setBars(){ super.setBars(); - addBar("progress", (UnitFactoryBuild e) -> new Bar("bar.progress", Pal.ammo, e::fraction)); + addBar("progress", (UnitFactoryBuild e) -> new Bar( + () -> Core.bundle.format("bar.progress", Strings.autoFixed(e.fraction() * 100f, 0)), + () -> Pal.ammo, + e::fraction + )); addBar("units", (UnitFactoryBuild e) -> new Bar( @@ -229,9 +233,7 @@ public class UnitFactory extends UnitBlock{ public boolean canSetCommand(){ var output = unit(); - return output != null && output.commands.size > 1 && output.allowChangeCommands && - //to avoid cluttering UI, don't show command selection for "standard" units that only have two commands. - !(output.commands.size == 2 && output.commands.get(1) == UnitCommand.enterPayloadCommand); + return output != null && output.commands.size > 1 && output.allowChangeCommands; } @Override @@ -258,6 +260,12 @@ public class UnitFactory extends UnitBlock{ @Override public void onCommand(Vec2 target){ commandPos = target; + if(command != null && command.snapToBuilding){ + var build = world.buildWorld(target.x, target.y); + if(build != null && build.team == this.team){ + commandPos.set(build); + } + } } @Override