diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index a91c40f95e..972e41e21d 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -378,6 +378,8 @@ stance.holdfire = Stance: Hold Fire stance.pursuetarget = Stance: Pursue Target stance.patrol = Stance: Patrol Path stance.ram = Stance: Ram\n[lightgray]Straight line movement, no pathfinding +stance.mineauto = Automatic Mining +stance.mine = Mine Item: {0} openlink = Open Link copylink = Copy Link back = Back diff --git a/core/src/mindustry/ai/ItemUnitStance.java b/core/src/mindustry/ai/ItemUnitStance.java new file mode 100644 index 0000000000..cfa5ec5f5e --- /dev/null +++ b/core/src/mindustry/ai/ItemUnitStance.java @@ -0,0 +1,33 @@ +package mindustry.ai; + +import arc.*; +import arc.scene.style.*; +import arc.struct.*; +import arc.util.*; +import mindustry.type.*; + +public class ItemUnitStance extends UnitStance{ + private static ObjectMap itemToStance = new ObjectMap<>(); + + public final Item item; + + public ItemUnitStance(Item item){ + super("item-" + item.name, "item-" + item.name, null); + this.item = item; + itemToStance.put(item, this); + } + + public static @Nullable ItemUnitStance getByItem(Item item){ + return itemToStance.get(item); + } + + @Override + public String localized(){ + return Core.bundle.format("stance.mine", item.localizedName); + } + + @Override + public TextureRegionDrawable getIcon(){ + return new TextureRegionDrawable(item.uiIcon); + } +} diff --git a/core/src/mindustry/ai/UnitCommand.java b/core/src/mindustry/ai/UnitCommand.java index 5708854109..23489bfeb7 100644 --- a/core/src/mindustry/ai/UnitCommand.java +++ b/core/src/mindustry/ai/UnitCommand.java @@ -26,6 +26,8 @@ public class UnitCommand extends MappableContent{ public boolean resetTarget = true; /** */ public boolean exactArrival = false; + /** If true, this command refreshes the list of stances when selected TODO: do not use, this will likely be removed later!*/ + public boolean refreshOnSelect = false; /** Key to press for this command. */ public @Nullable Binding keybind = null; @@ -76,7 +78,9 @@ public class UnitCommand extends MappableContent{ ai.onlyAssist = true; return ai; }); - mineCommand = new UnitCommand("mine", "production", Binding.unit_command_mine, u -> new MinerAI()); + mineCommand = new UnitCommand("mine", "production", Binding.unit_command_mine, u -> new MinerAI()){{ + refreshOnSelect = true; + }}; boostCommand = new UnitCommand("boost", "up", Binding.unit_command_boost, u -> new BoostAI()){{ switchToMove = false; drawTarget = true; diff --git a/core/src/mindustry/ai/UnitStance.java b/core/src/mindustry/ai/UnitStance.java index 4fb6ba5c30..3e5d8ccdb0 100644 --- a/core/src/mindustry/ai/UnitStance.java +++ b/core/src/mindustry/ai/UnitStance.java @@ -3,12 +3,14 @@ package mindustry.ai; import arc.*; import arc.scene.style.*; import arc.util.*; +import mindustry.*; import mindustry.ctype.*; import mindustry.gen.*; import mindustry.input.*; +import mindustry.type.*; public class UnitStance extends MappableContent{ - public static UnitStance stop, shoot, holdFire, pursueTarget, patrol, ram; + public static UnitStance stop, shoot, holdFire, pursueTarget, patrol, ram, mineAuto; /** Name of UI icon (from Icon class). */ public String icon; @@ -30,7 +32,7 @@ public class UnitStance extends MappableContent{ } public char getEmoji() { - return (char) Iconc.codes.get(icon, Iconc.cancel); + return (char)Iconc.codes.get(icon, Iconc.cancel); } @Override @@ -50,5 +52,11 @@ public class UnitStance extends MappableContent{ pursueTarget = new UnitStance("pursuetarget", "right", Binding.unit_stance_pursue_target); patrol = new UnitStance("patrol", "refresh", Binding.unit_stance_patrol); ram = new UnitStance("ram", "rightOpen", Binding.unit_stance_ram); + mineAuto = new UnitStance("mineauto", "settings", null); + + //Only vanilla items are supported for now + for(Item item : Vars.content.items()){ + new ItemUnitStance(item); + } } } diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 452ee32bea..22150cddce 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -77,6 +77,11 @@ public class CommandAI extends AIController{ //this should not be possible if(stance == UnitStance.stop) stance = UnitStance.shoot; + //fix incorrect stance when mining + if(command == UnitCommand.mineCommand && stance != UnitStance.mineAuto && !(stance instanceof ItemUnitStance)){ + stance = UnitStance.mineAuto; + } + //pursue the target if relevant if(stance == UnitStance.pursueTarget && target != null && attackTarget == null && targetPos == null){ commandTarget(target, false); diff --git a/core/src/mindustry/ai/types/MinerAI.java b/core/src/mindustry/ai/types/MinerAI.java index ff66ab6116..598e479515 100644 --- a/core/src/mindustry/ai/types/MinerAI.java +++ b/core/src/mindustry/ai/types/MinerAI.java @@ -1,5 +1,6 @@ package mindustry.ai.types; +import mindustry.ai.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.type.*; @@ -22,8 +23,12 @@ public class MinerAI extends AIController{ unit.mineTile(null); } + Item autoItem = unit.controller() instanceof CommandAI ai && ai.stance instanceof ItemUnitStance stance ? stance.item : null; + if(mining){ - if(timer.get(timerTarget2, 60 * 4) || targetItem == null){ + if(autoItem != null){ + targetItem = autoItem; + }else if(timer.get(timerTarget2, 60 * 4) || targetItem == null){ targetItem = unit.type.mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i)); } diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 4a86ded7f5..9685485f45 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -1256,11 +1256,7 @@ public class UnitTypes{ //region air support mono = new UnitType("mono"){{ - //there's no reason to command monos anywhere. it's just annoying. - controller = u -> new MinerAI(); - defaultCommand = UnitCommand.mineCommand; - allowChangeCommands = false; flying = true; drag = 0.06f; diff --git a/core/src/mindustry/core/ContentLoader.java b/core/src/mindustry/core/ContentLoader.java index 4e2f86410a..1ef48b5018 100644 --- a/core/src/mindustry/core/ContentLoader.java +++ b/core/src/mindustry/core/ContentLoader.java @@ -43,9 +43,9 @@ public class ContentLoader{ /** Creates all base types. */ public void createBaseContent(){ UnitCommand.loadAll(); - UnitStance.loadAll(); TeamEntries.load(); Items.load(); + UnitStance.loadAll(); //needs to access items StatusEffects.load(); Liquids.load(); Bullets.load(); diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index 0279c542bc..f4a0586bfa 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -54,6 +54,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ /** Used for dropping items. */ final static float playerSelectRange = mobile ? 17f : 11f; final static float unitSelectRadScl = 1f; + final static Seq stancesOut = new Seq<>(); final static IntSeq removed = new IntSeq(); final static IntSet intSet = new IntSet(); /** Maximum line length. */ @@ -369,6 +370,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ ai.attackTarget = null; } unit.lastCommanded = player.coloredName(); + + //make sure its stance is valid + stancesOut.clear(); + unit.type.getUnitStances(unit, stancesOut); + if(stancesOut.size > 0 && !stancesOut.contains(ai.stance)){ + ai.stance = stancesOut.first(); + } } } } diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index fd189bfc7f..ce323042e1 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -592,6 +592,24 @@ public class UnitType extends UnlockableContent implements Senseable{ return hittable || (vulnerableWithPayloads && unit instanceof Payloadc p && p.hasPayload()); } + /** Adds all available unit stances based on the unit's current state. This can change based on the command of the unit. */ + public void getUnitStances(Unit unit, Seq out){ + //return mining stances based on present items + if(unit.controller() instanceof CommandAI ai && ai.currentCommand() == UnitCommand.mineCommand){ + out.add(UnitStance.mineAuto); + for(Item item : indexer.getAllPresentOres()){ + if(unit.canMine(item)){ + var itemStance = ItemUnitStance.getByItem(item); + if(itemStance != null){ + out.add(itemStance); + } + } + } + }else{ + out.addAll(stances); + } + } + public void update(Unit unit){ } diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java index e14edc448c..e83e907449 100644 --- a/core/src/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/mindustry/ui/fragments/PlacementFragment.java @@ -468,17 +468,33 @@ public class PlacementFragment{ UnitStance[] currentStance = {null}; var stances = new Seq(); + var stancesOut = new Seq(); + rebuildCommand = () -> { u.clearChildren(); var units = control.input.selectedUnits; if(units.size > 0){ - int[] counts = new int[content.units().size]; - for(var unit : units){ - counts[unit.type.id] ++; - } commands.clear(); stances.clear(); + boolean firstCommand = false, firstStance = false; + int[] counts = new int[content.units().size]; + + for(var unit : units){ + counts[unit.type.id] ++; + + stancesOut.clear(); + unit.type.getUnitStances(unit, stancesOut); + + if(!firstStance){ + stances.add(stancesOut); + firstStance = true; + }else{ + //remove commands that this next unit type doesn't have + stances.removeAll(st -> !stancesOut.contains(st)); + } + } + Table unitlist = u.table().growX().left().get(); unitlist.left(); @@ -520,14 +536,6 @@ public class PlacementFragment{ //remove commands that this next unit type doesn't have commands.removeAll(com -> !type.commands.contains(com)); } - - if(!firstStance){ - stances.add(type.stances); - firstStance = true; - }else{ - //remove commands that this next unit type doesn't have - stances.removeAll(st -> !type.stances.contains(st)); - } } } @@ -562,7 +570,7 @@ public class PlacementFragment{ int scol = 0; for(var stance : stances){ - coms.button(Icon.icons.get(stance.icon, Icon.cancel), Styles.clearNoneTogglei, () -> { + coms.button(stance.getIcon(), Styles.clearNoneTogglei, () -> { Call.setUnitStance(player, units.mapInt(un -> un.id).toArray(), stance); }).checked(i -> currentStance[0] == stance).size(50f).tooltip(stance.localized(), true); @@ -608,15 +616,15 @@ public class PlacementFragment{ } } - currentCommand[0] = shareCommand; - currentStance[0] = shareStance; - int size = control.input.selectedUnits.size; - if(curCount[0] != size){ + if(curCount[0] != size || (currentCommand[0] != shareCommand && currentCommand[0] != null && (currentCommand[0].refreshOnSelect || shareCommand.refreshOnSelect))){ curCount[0] = size; rebuildCommand.run(); } + currentCommand[0] = shareCommand; + currentStance[0] = shareStance; + //not a huge fan of running input logic here, but it's convenient as the stance arrays are all here... for(UnitStance stance : stances){ //first stance must always be the stop stance