From ec8262418fb2f4e604eb9463d591f4f07971aec9 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 11 Feb 2023 17:40:57 -0500 Subject: [PATCH] Basic unit boost command support --- core/assets/bundles/bundle.properties | 1 + core/src/mindustry/ai/UnitCommand.java | 18 ++++++++-- core/src/mindustry/ai/types/BoostAI.java | 21 ++++++++++++ core/src/mindustry/ai/types/CommandAI.java | 33 ++++++++++++------- .../src/mindustry/entities/comp/UnitComp.java | 2 +- .../entities/units/AIController.java | 4 +++ core/src/mindustry/input/InputHandler.java | 17 ++++++---- core/src/mindustry/type/UnitType.java | 4 +++ .../ui/fragments/PlacementFragment.java | 2 +- 9 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 core/src/mindustry/ai/types/BoostAI.java diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 5c66b03c4f..dc67fbb815 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -339,6 +339,7 @@ command.repair = Repair command.rebuild = Rebuild command.assist = Assist Player command.move = Move +command.boost = Boost openlink = Open Link copylink = Copy Link back = Back diff --git a/core/src/mindustry/ai/UnitCommand.java b/core/src/mindustry/ai/UnitCommand.java index a3a20788d1..c04e3570a2 100644 --- a/core/src/mindustry/ai/UnitCommand.java +++ b/core/src/mindustry/ai/UnitCommand.java @@ -14,7 +14,10 @@ public class UnitCommand{ public static final UnitCommand - moveCommand = new UnitCommand("move", "right", u -> null), + moveCommand = new UnitCommand("move", "right", u -> null){{ + drawTarget = true; + resetTarget = false; + }}, repairCommand = new UnitCommand("repair", "modeSurvival", u -> new RepairAI()), rebuildCommand = new UnitCommand("rebuild", "hammer", u -> new BuilderAI()), assistCommand = new UnitCommand("assist", "players", u -> { @@ -22,7 +25,12 @@ public class UnitCommand{ ai.onlyAssist = true; return ai; }), - mineCommand = new UnitCommand("mine", "production", u -> new MinerAI()); + mineCommand = new UnitCommand("mine", "production", u -> new MinerAI()), + boostCommand = new UnitCommand("boost", "up", u -> new BoostAI()){{ + switchToMove = false; + drawTarget = true; + resetTarget = false; + }}; /** Unique ID number. */ public final int id; @@ -32,6 +40,12 @@ public class UnitCommand{ public final String icon; /** Controller that this unit will use when this command is used. Return null for "default" behavior. */ public final Func controller; + /** If true, this unit will automatically switch away to the move command when given a position. */ + public boolean switchToMove = true; + /** Whether to draw the movement/attack target. */ + public boolean drawTarget = false; + /** Whether to reset targets when switching to or from this command. */ + public boolean resetTarget = true; public UnitCommand(String name, String icon, Func controller){ this.name = name; diff --git a/core/src/mindustry/ai/types/BoostAI.java b/core/src/mindustry/ai/types/BoostAI.java new file mode 100644 index 0000000000..bed0428c5d --- /dev/null +++ b/core/src/mindustry/ai/types/BoostAI.java @@ -0,0 +1,21 @@ +package mindustry.ai.types; + +import mindustry.ai.*; +import mindustry.entities.units.*; + +//not meant to be used outside RTS-AI-controlled units +public class BoostAI extends AIController{ + + @Override + public void updateUnit(){ + if(unit.controller() instanceof CommandAI ai){ + ai.defaultBehavior(); + unit.updateBoosting(true); + + //auto land when near target + if(ai.attackTarget != null && unit.within(ai.attackTarget, unit.range())){ + unit.command().command(UnitCommand.moveCommand); + } + } + } +} diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 3bd2bb33f6..794fd41a3e 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -35,8 +35,8 @@ public class CommandAI extends AIController{ /** Last command type assigned. Used for detecting command changes. */ protected @Nullable UnitCommand lastCommand; - public @Nullable UnitCommand currentCommand(){ - return command; + public UnitCommand currentCommand(){ + return command == null ? UnitCommand.moveCommand : command; } /** Attempts to assign a command to this unit. If not supported by the unit type, does nothing. */ @@ -62,7 +62,7 @@ public class CommandAI extends AIController{ } //update command controller based on index. - var curCommand = currentCommand(); + var curCommand = command; if(lastCommand != curCommand){ lastCommand = curCommand; commandController = (curCommand == null ? null : curCommand.controller.get(unit)); @@ -72,8 +72,14 @@ public class CommandAI extends AIController{ if(commandController != null){ if(commandController.unit() != unit) commandController.unit(unit); commandController.updateUnit(); - return; + }else{ + defaultBehavior(); + //boosting control is not supported, so just don't. + unit.updateBoosting(false); } + } + + public void defaultBehavior(){ //acquiring naval targets isn't supported yet, so use the fallback dumb AI if(unit.team.isAI() && unit.team.rules().rtsAi && unit.type.naval){ @@ -162,10 +168,10 @@ public class CommandAI extends AIController{ circleAttack(80f); }else{ moveTo(vecOut, - attackTarget != null && unit.within(attackTarget, engageRange) ? engageRange : - unit.isGrounded() ? 0f : - attackTarget != null ? engageRange : - 0f, unit.isFlying() ? 40f : 100f, false, null, targetPos.epsilonEquals(vecOut, 4.1f)); + attackTarget != null && unit.within(attackTarget, engageRange) ? engageRange : + unit.isGrounded() ? 0f : + attackTarget != null ? engageRange : + 0f, unit.isFlying() ? 40f : 100f, false, null, targetPos.epsilonEquals(vecOut, 4.1f)); } } @@ -207,9 +213,6 @@ public class CommandAI extends AIController{ }else if(target != null){ faceTarget(); } - - //boosting control is not supported, so just don't. - unit.updateBoosting(false); } @Override @@ -249,8 +252,12 @@ public class CommandAI extends AIController{ lastTargetPos = targetPos; } + @Override public void commandPosition(Vec2 pos){ commandPosition(pos, false); + if(commandController != null){ + commandController.commandPosition(pos); + } } public void commandPosition(Vec2 pos, boolean stopWhenInRange){ @@ -261,8 +268,12 @@ public class CommandAI extends AIController{ this.stopWhenInRange = stopWhenInRange; } + @Override public void commandTarget(Teamc moveTo){ commandTarget(moveTo, false); + if(commandController != null){ + commandController.commandTarget(moveTo); + } } public void commandTarget(Teamc moveTo, boolean stopAtTarget){ diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 2938c2f37e..88bf6bd672 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -64,7 +64,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I } public void updateBoosting(boolean boost){ - if(!type.canBoost) return; + if(!type.canBoost || dead) return; elevation = Mathf.approachDelta(elevation, type.canBoost ? Mathf.num(boost || onSolid() || (isFlying() && !canLand())) : 0f, type.riseSpeed); } diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 92b79cfd05..c46abd48bb 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -222,6 +222,10 @@ public class AIController implements UnitController{ return target(x, y, range, air, ground); } + public void commandTarget(Teamc moveTo){} + + public void commandPosition(Vec2 pos){} + /** Called after this controller is assigned a unit. */ public void init(){ diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index d6c8d5d2f4..78009634ca 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -231,10 +231,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ if(unit != null && unit.team == player.team() && unit.controller() instanceof CommandAI ai){ //implicitly order it to move - ai.command(UnitCommand.moveCommand); + if(ai.command == null || ai.command.switchToMove){ + ai.command(UnitCommand.moveCommand); + } if(teamTarget != null && teamTarget.team() != player.team()){ ai.commandTarget(teamTarget); + }else if(posTarget != null){ ai.commandPosition(posTarget); } @@ -269,10 +272,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ for(int id : unitIds){ Unit unit = Groups.unit.getByID(id); if(unit != null && unit.team == player.team() && unit.controller() instanceof CommandAI ai){ + boolean reset = command.resetTarget || ai.currentCommand().resetTarget; ai.command(command); - //reset targeting - ai.targetPos = null; - ai.attackTarget = null; + if(reset){ + ai.targetPos = null; + ai.attackTarget = null; + } unit.lastCommanded = player.coloredName(); } } @@ -868,7 +873,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ for(Unit unit : selectedUnits){ CommandAI ai = unit.command(); //draw target line - if(ai.targetPos != null && ai.command == UnitCommand.moveCommand){ + if(ai.targetPos != null && ai.currentCommand().drawTarget){ Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos; Drawf.limitLine(unit, lineDest, unit.hitSize / 2f, 3.5f); @@ -880,7 +885,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ Drawf.square(unit.x, unit.y, unit.hitSize / 1.4f + 1f); //TODO when to draw, when to not? - if(ai.attackTarget != null && ai.command == UnitCommand.moveCommand){ + if(ai.attackTarget != null && ai.currentCommand().drawTarget){ Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove); } } diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 67c85ca93c..d40b5cc10a 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -805,6 +805,10 @@ public class UnitType extends UnlockableContent{ cmds.add(UnitCommand.moveCommand); + if(canBoost){ + cmds.add(UnitCommand.boostCommand); + } + //healing, mining and building is only supported for flying units; pathfinding to ambiguously reachable locations is hard. if(flying){ if(canHeal){ diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java index 8195baa9aa..e1d659199d 100644 --- a/core/src/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/mindustry/ui/fragments/PlacementFragment.java @@ -514,7 +514,7 @@ public class PlacementFragment{ //find the command that all units have, or null if they do not share one for(var unit : control.input.selectedUnits){ if(unit.isCommandable()){ - var nextCommand = unit.command().currentCommand(); + var nextCommand = unit.command().command; if(hadCommand){ if(shareCommand != nextCommand){