mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-10-06 04:47:14 +03:00
WIP command order system
This commit is contained in:
parent
a326e36bbe
commit
55edd53f84
@ -311,6 +311,11 @@ open = Open
|
||||
customize = Customize Rules
|
||||
cancel = Cancel
|
||||
command = Command
|
||||
command.mine = Mine
|
||||
command.repair = Repair
|
||||
command.rebuild = Rebuild
|
||||
command.assist = Assist Player
|
||||
command.move = Move
|
||||
openlink = Open Link
|
||||
copylink = Copy Link
|
||||
back = Back
|
||||
|
52
core/src/mindustry/ai/UnitCommand.java
Normal file
52
core/src/mindustry/ai/UnitCommand.java
Normal file
@ -0,0 +1,52 @@
|
||||
package mindustry.ai;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
/** Defines a pattern of behavior that an RTS-controlled unit should follow. Shows up in the command UI. */
|
||||
public class UnitCommand{
|
||||
/** List of all commands by ID. */
|
||||
public static final Seq<UnitCommand> all = new Seq<>();
|
||||
|
||||
public static final UnitCommand
|
||||
|
||||
//TODO they do not use the command "interface" or designation at all
|
||||
moveCommand = new UnitCommand("move", "right", u -> null),
|
||||
repairCommand = new UnitCommand("repair", "modeSurvival", u -> new RepairAI()),
|
||||
rebuildCommand = new UnitCommand("rebuild", "hammer", u -> new BuilderAI()),
|
||||
assistCommand = new UnitCommand("assist", "players", u -> {
|
||||
var ai = new BuilderAI();
|
||||
ai.onlyAssist = true;
|
||||
return ai;
|
||||
}),
|
||||
mineCommand = new UnitCommand("mine", "production", u -> new MinerAI());
|
||||
|
||||
/** Default set of specified commands. */
|
||||
public static final UnitCommand[] defaultCommands = {moveCommand};
|
||||
|
||||
/** Unique ID number. */
|
||||
public final int id;
|
||||
/** Named used for tooltip/description. */
|
||||
public final String name;
|
||||
/** Name of UI icon (from Icon class). */
|
||||
public final String icon;
|
||||
/** Controller that this unit will use when this command is used. Return null for "default" behavior. */
|
||||
public final Func<Unit, AIController> controller;
|
||||
|
||||
public UnitCommand(String name, String icon, Func<Unit, AIController> controller){
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.controller = controller;
|
||||
|
||||
id = all.size;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
return Core.bundle.get("command." + name);
|
||||
}
|
||||
}
|
@ -14,12 +14,14 @@ import static mindustry.Vars.*;
|
||||
public class BuilderAI extends AIController{
|
||||
public static float buildRadius = 1500, retreatDst = 110f, retreatDelay = Time.toSeconds * 2f;
|
||||
|
||||
public @Nullable Unit assistFollowing;
|
||||
public @Nullable Unit following;
|
||||
public @Nullable Teamc enemy;
|
||||
public @Nullable BlockPlan lastPlan;
|
||||
|
||||
public float fleeRange = 370f;
|
||||
public boolean alwaysFlee;
|
||||
public boolean onlyAssist;
|
||||
|
||||
boolean found = false;
|
||||
float retreatTimer;
|
||||
@ -41,6 +43,10 @@ public class BuilderAI extends AIController{
|
||||
|
||||
unit.updateBuilding = true;
|
||||
|
||||
if(assistFollowing != null && assistFollowing.activelyBuilding()){
|
||||
following = assistFollowing;
|
||||
}
|
||||
|
||||
if(following != null){
|
||||
retreatTimer = 0f;
|
||||
//try to follow and mimic someone
|
||||
@ -108,6 +114,10 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
}else{
|
||||
|
||||
if(assistFollowing != null){
|
||||
moveTo(assistFollowing, assistFollowing.type.hitSize * 1.5f + 60f);
|
||||
}
|
||||
|
||||
//follow someone and help them build
|
||||
if(timer.get(timerTarget2, 60f)){
|
||||
found = false;
|
||||
@ -130,13 +140,29 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(onlyAssist){
|
||||
float minDst = Float.MAX_VALUE;
|
||||
Player closest = null;
|
||||
for(var player : Groups.player){
|
||||
if(player.unit().canBuild() && !player.dead()){
|
||||
float dst = player.dst2(unit);
|
||||
if(dst < minDst){
|
||||
closest = player;
|
||||
minDst = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assistFollowing = closest == null ? null : closest.unit();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this is bad, rebuild time should not depend on AI here
|
||||
float rebuildTime = (unit.team.rules().rtsAi ? 12f : 2f) * 60f;
|
||||
|
||||
//find new plan
|
||||
if(!unit.team.data().plans.isEmpty() && following == null && timer.get(timerTarget3, rebuildTime)){
|
||||
if(!onlyAssist && !unit.team.data().plans.isEmpty() && following == null && timer.get(timerTarget3, rebuildTime)){
|
||||
Queue<BlockPlan> blocks = unit.team.data().plans;
|
||||
BlockPlan block = blocks.first();
|
||||
|
||||
|
@ -12,20 +12,61 @@ import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
public class CommandAI extends AIController{
|
||||
private static final float localInterval = 40f;
|
||||
private static final Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2();
|
||||
protected static final float localInterval = 40f;
|
||||
protected static final Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2();
|
||||
|
||||
public @Nullable Vec2 targetPos;
|
||||
public @Nullable Teamc attackTarget;
|
||||
|
||||
private boolean stopAtTarget;
|
||||
private Vec2 lastTargetPos;
|
||||
private int pathId = -1;
|
||||
private Seq<Unit> local = new Seq<>(false);
|
||||
private boolean flocked;
|
||||
protected boolean stopAtTarget;
|
||||
protected Vec2 lastTargetPos;
|
||||
protected int pathId = -1;
|
||||
protected Seq<Unit> local = new Seq<>(false);
|
||||
protected boolean flocked;
|
||||
|
||||
/** Current command this unit is following. */
|
||||
public @Nullable UnitCommand command;
|
||||
/** Current controller instance based on command. */
|
||||
protected @Nullable AIController commandController;
|
||||
/** Last command type assigned. Used for detecting command changes. */
|
||||
protected @Nullable UnitCommand lastCommand;
|
||||
|
||||
public @Nullable UnitCommand currentCommand(){
|
||||
return command;
|
||||
}
|
||||
|
||||
/** Attempts to assign a command to this unit. If not supported by the unit type, does nothing. */
|
||||
public void command(UnitCommand command){
|
||||
if(Structs.contains(unit.type.commands, command)){
|
||||
//clear old state.
|
||||
unit.mineTile = null;
|
||||
unit.clearBuilding();
|
||||
this.command = command;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
|
||||
//assign defaults
|
||||
if(command == null && unit.type.commands.length > 0){
|
||||
command = unit.type.defaultCommand == null ? unit.type.commands[0] : unit.type.defaultCommand;
|
||||
}
|
||||
|
||||
//update command controller based on index.
|
||||
var curCommand = currentCommand();
|
||||
if(lastCommand != curCommand){
|
||||
lastCommand = curCommand;
|
||||
commandController = (curCommand == null ? null : curCommand.controller.get(unit));
|
||||
}
|
||||
|
||||
//use the command controller if it is provided, and bail out.
|
||||
if(commandController != null){
|
||||
if(commandController.unit() != unit) commandController.unit(unit);
|
||||
commandController.updateUnit();
|
||||
return;
|
||||
}
|
||||
|
||||
updateVisuals();
|
||||
updateTargeting();
|
||||
|
||||
@ -104,6 +145,7 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
|
||||
if(attackTarget == null){
|
||||
//TODO overshoot.
|
||||
if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){
|
||||
targetPos = null;
|
||||
}else if(local.size > 1){
|
||||
@ -134,10 +176,55 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keepState(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
return attackTarget == null || !attackTarget.within(x, y, range + 3f + (attackTarget instanceof Sized s ? s.hitSize()/2f : 0f)) ? super.findTarget(x, y, range, air, ground) : attackTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retarget(){
|
||||
//retarget faster when there is an explicit target
|
||||
return attackTarget != null ? timer.get(timerTarget, 10) : timer.get(timerTarget, 20);
|
||||
}
|
||||
|
||||
public boolean hasCommand(){
|
||||
return targetPos != null;
|
||||
}
|
||||
|
||||
public void setupLastPos(){
|
||||
lastTargetPos = targetPos;
|
||||
}
|
||||
|
||||
public void commandPosition(Vec2 pos){
|
||||
targetPos = pos;
|
||||
lastTargetPos = pos;
|
||||
attackTarget = null;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
public void commandTarget(Teamc moveTo){
|
||||
commandTarget(moveTo, false);
|
||||
}
|
||||
|
||||
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
|
||||
attackTarget = moveTo;
|
||||
this.stopAtTarget = stopAtTarget;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
//TODO ひどい
|
||||
(does not work)
|
||||
|
||||
public static float cohesionScl = 0.3f;
|
||||
public static float cohesionRad = 3f, separationRad = 1.1f, separationScl = 1f, flockMult = 0.5f;
|
||||
|
||||
//TODO ひどい
|
||||
Vec2 calculateFlock(){
|
||||
if(local.isEmpty()) return flockVec.setZero();
|
||||
|
||||
@ -177,47 +264,5 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
|
||||
return flockVec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keepState(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
return attackTarget == null || !attackTarget.within(x, y, range + 3f + (attackTarget instanceof Sized s ? s.hitSize()/2f : 0f)) ? super.findTarget(x, y, range, air, ground) : attackTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retarget(){
|
||||
//retarget faster when there is an explicit target
|
||||
return attackTarget != null ? timer.get(timerTarget, 10) : timer.get(timerTarget, 20);
|
||||
}
|
||||
|
||||
public boolean hasCommand(){
|
||||
return targetPos != null;
|
||||
}
|
||||
|
||||
public void setupLastPos(){
|
||||
lastTargetPos = targetPos;
|
||||
|
||||
}
|
||||
|
||||
public void commandPosition(Vec2 pos){
|
||||
targetPos = pos;
|
||||
lastTargetPos = pos;
|
||||
attackTarget = null;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
public void commandTarget(Teamc moveTo){
|
||||
commandTarget(moveTo, false);
|
||||
}
|
||||
|
||||
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
|
||||
attackTarget = moveTo;
|
||||
this.stopAtTarget = stopAtTarget;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.*;
|
||||
@ -1247,8 +1248,11 @@ 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;
|
||||
|
||||
flying = true;
|
||||
drag = 0.06f;
|
||||
accel = 0.12f;
|
||||
@ -1266,7 +1270,7 @@ public class UnitTypes{
|
||||
}};
|
||||
|
||||
poly = new UnitType("poly"){{
|
||||
controller = u -> new BuilderAI();
|
||||
defaultCommand = UnitCommand.rebuildCommand;
|
||||
|
||||
flying = true;
|
||||
drag = 0.05f;
|
||||
@ -1320,7 +1324,7 @@ public class UnitTypes{
|
||||
}};
|
||||
|
||||
mega = new UnitType("mega"){{
|
||||
controller = u -> new RepairAI();
|
||||
defaultCommand = UnitCommand.repairCommand;
|
||||
|
||||
mineTier = 3;
|
||||
mineSpeed = 4f;
|
||||
|
@ -82,6 +82,8 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
|
||||
|
||||
buildCounter += Time.delta;
|
||||
if(Float.isNaN(buildCounter) || Float.isInfinite(buildCounter)) buildCounter = 0f;
|
||||
buildCounter = Math.min(buildCounter, 10f);
|
||||
|
||||
while(buildCounter >= 1){
|
||||
buildCounter -= 1f;
|
||||
|
@ -14,6 +14,7 @@ import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
@ -228,6 +229,10 @@ 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){
|
||||
|
||||
//implicitly order it to move
|
||||
ai.command(UnitCommand.moveCommand);
|
||||
|
||||
if(teamTarget != null && teamTarget.team() != player.team()){
|
||||
ai.commandTarget(teamTarget);
|
||||
}else if(posTarget != null){
|
||||
@ -246,6 +251,28 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void setUnitCommand(Player player, int[] unitIds, UnitCommand command){
|
||||
if(player == null || unitIds == null || command == null) return;
|
||||
|
||||
if(net.server() && !netServer.admins.allowAction(player, ActionType.commandUnits, event -> {
|
||||
event.unitIDs = unitIds;
|
||||
})){
|
||||
throw new ValidateException(player, "Player cannot command units.");
|
||||
}
|
||||
|
||||
for(int id : unitIds){
|
||||
Unit unit = Groups.unit.getByID(id);
|
||||
if(unit != null && unit.team == player.team() && unit.controller() instanceof CommandAI ai){
|
||||
ai.command(command);
|
||||
//reset targeting
|
||||
ai.targetPos = null;
|
||||
ai.attackTarget = null;
|
||||
unit.lastCommanded = player.coloredName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void commandBuilding(Player player, Building build, Vec2 target){
|
||||
if(player == null || build == null || build.team != player.team() || !build.block.commandable || target == null) return;
|
||||
@ -814,7 +841,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
for(Unit unit : selectedUnits){
|
||||
CommandAI ai = unit.command();
|
||||
//draw target line
|
||||
if(ai.targetPos != null){
|
||||
if(ai.targetPos != null && ai.command == UnitCommand.moveCommand){
|
||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
Drawf.limitLine(unit, lineDest, unit.hitSize / 2f, 3.5f);
|
||||
|
||||
@ -825,7 +852,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
Drawf.square(unit.x, unit.y, unit.hitSize / 1.4f + 1f);
|
||||
|
||||
if(ai.attackTarget != null){
|
||||
//TODO when to draw, when to not?
|
||||
if(ai.attackTarget != null && ai.command == UnitCommand.moveCommand){
|
||||
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
@ -283,6 +284,14 @@ public class TypeIO{
|
||||
return Nulls.unit;
|
||||
}
|
||||
|
||||
public static void writeCommand(Writes write, UnitCommand command){
|
||||
write.b(command.id);
|
||||
}
|
||||
|
||||
public static UnitCommand readCommand(Reads read){
|
||||
return UnitCommand.all.get(read.ub());
|
||||
}
|
||||
|
||||
public static void writeEntity(Writes write, Entityc entity){
|
||||
write.i(entity == null ? -1 : entity.id());
|
||||
}
|
||||
@ -441,7 +450,7 @@ public class TypeIO{
|
||||
write.b(3);
|
||||
write.i(logic.controller.pos());
|
||||
}else if(control instanceof CommandAI ai){
|
||||
write.b(4);
|
||||
write.b(6);
|
||||
write.bool(ai.attackTarget != null);
|
||||
write.bool(ai.targetPos != null);
|
||||
|
||||
@ -457,6 +466,7 @@ public class TypeIO{
|
||||
write.i(((Unit)ai.attackTarget).id);
|
||||
}
|
||||
}
|
||||
write.b(ai.command == null ? -1 : ai.command.id);
|
||||
}else if(control instanceof AssemblerAI){ //hate
|
||||
write.b(5);
|
||||
}else{
|
||||
@ -488,7 +498,8 @@ public class TypeIO{
|
||||
out.controller = world.build(pos);
|
||||
return out;
|
||||
}
|
||||
}else if(type == 4){
|
||||
//type 4 is the old CommandAI with no commandIndex, type 6 is the new one with the index as a single byte.
|
||||
}else if(type == 4 || type == 6){
|
||||
CommandAI ai = prev instanceof CommandAI pai ? pai : new CommandAI();
|
||||
|
||||
boolean hasAttack = read.bool(), hasPos = read.bool();
|
||||
@ -511,6 +522,11 @@ public class TypeIO{
|
||||
ai.attackTarget = null;
|
||||
}
|
||||
|
||||
if(type == 6){
|
||||
byte id = read.b();
|
||||
ai.command = id < 0 ? null : UnitCommand.all.get(id);
|
||||
}
|
||||
|
||||
return ai;
|
||||
}else if(type == 5){
|
||||
//augh
|
||||
|
@ -277,6 +277,11 @@ public class UnitType extends UnlockableContent{
|
||||
/** Flags to target based on priority. Null indicates that the closest target should be found. The closest enemy core is used as a fallback. */
|
||||
public BlockFlag[] targetFlags = {null};
|
||||
|
||||
/** Commands available to this unit through RTS controls. An empty array means commands will be assigned based on unit capabilities in init(). */
|
||||
public UnitCommand[] commands = {};
|
||||
/** Command to assign to this unit upon creation. Null indicates the first command in the array. */
|
||||
public @Nullable UnitCommand defaultCommand;
|
||||
|
||||
/** color for outline generated around sprites */
|
||||
public Color outlineColor = Pal.darkerMetal;
|
||||
/** thickness for sprite outline */
|
||||
@ -772,6 +777,33 @@ public class UnitType extends UnlockableContent{
|
||||
|
||||
canAttack = weapons.contains(w -> !w.noAttack);
|
||||
|
||||
//assign default commands.
|
||||
if(commands.length == 0){
|
||||
Seq<UnitCommand> cmds = new Seq<>(UnitCommand.class);
|
||||
|
||||
//TODO ????
|
||||
//if(canAttack){
|
||||
cmds.add(UnitCommand.moveCommand);
|
||||
//}
|
||||
|
||||
//healing, mining and building is only supported for flying units; pathfinding to ambiguously reachable locations is hard.
|
||||
if(flying){
|
||||
if(canHeal){
|
||||
cmds.add(UnitCommand.repairCommand);
|
||||
}
|
||||
|
||||
if(buildSpeed > 0){
|
||||
cmds.add(UnitCommand.rebuildCommand, UnitCommand.assistCommand);
|
||||
}
|
||||
|
||||
if(mineSpeed > 0){
|
||||
cmds.add(UnitCommand.mineCommand);
|
||||
}
|
||||
}
|
||||
|
||||
commands = cmds.toArray();
|
||||
}
|
||||
|
||||
//dynamically create ammo capacity based on firing rate
|
||||
if(ammoCapacity < 0){
|
||||
float shotsPerSecond = weapons.sumf(w -> w.useAmmo ? 60f / w.reload : 0f);
|
||||
|
@ -11,6 +11,8 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.*;
|
||||
@ -422,6 +424,8 @@ public class PlacementFragment{
|
||||
commandTable.table(u -> {
|
||||
u.left();
|
||||
int[] curCount = {0};
|
||||
UnitCommand[] currentCommand = {null};
|
||||
var commands = new Seq<UnitCommand>();
|
||||
|
||||
Runnable rebuildCommand = () -> {
|
||||
u.clearChildren();
|
||||
@ -431,12 +435,17 @@ public class PlacementFragment{
|
||||
for(var unit : units){
|
||||
counts[unit.type.id] ++;
|
||||
}
|
||||
commands.clear();
|
||||
boolean firstCommand = false;
|
||||
Table unitlist = u.table().growX().left().get();
|
||||
unitlist.left();
|
||||
|
||||
int col = 0;
|
||||
for(int i = 0; i < counts.length; i++){
|
||||
if(counts[i] > 0){
|
||||
var type = content.unit(i);
|
||||
u.add(new ItemImage(type.uiIcon, counts[i])).tooltip(type.localizedName).pad(4).with(b -> {
|
||||
ClickListener listener = new ClickListener();
|
||||
unitlist.add(new ItemImage(type.uiIcon, counts[i])).tooltip(type.localizedName).pad(4).with(b -> {
|
||||
var listener = new ClickListener();
|
||||
|
||||
//left click -> select
|
||||
b.clicked(KeyCode.mouseLeft, () -> control.input.selectedUnits.removeAll(unit -> unit.type != type));
|
||||
@ -450,16 +459,62 @@ public class PlacementFragment{
|
||||
});
|
||||
|
||||
if(++col % 7 == 0){
|
||||
u.row();
|
||||
unitlist.row();
|
||||
}
|
||||
|
||||
if(!firstCommand){
|
||||
commands.add(type.commands);
|
||||
firstCommand = true;
|
||||
}else{
|
||||
//remove commands that this next unit type doesn't have
|
||||
commands.removeAll(com -> !Structs.contains(type.commands, com));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(commands.size > 1){
|
||||
u.row();
|
||||
|
||||
u.table(coms -> {
|
||||
for(var command : commands){
|
||||
coms.button(Icon.icons.get(command.icon, Icon.cancel), Styles.clearNoneTogglei, () -> {
|
||||
IntSeq ids = new IntSeq();
|
||||
for(var unit : units){
|
||||
ids.add(unit.id);
|
||||
}
|
||||
|
||||
Call.setUnitCommand(Vars.player, ids.toArray(), command);
|
||||
}).checked(i -> currentCommand[0] == command).size(50f).tooltip(command.localized());
|
||||
}
|
||||
}).fillX().padTop(4f).left();
|
||||
}
|
||||
}else{
|
||||
u.add("[no units]").color(Color.lightGray).growX().center().labelAlign(Align.center).pad(6);
|
||||
}
|
||||
};
|
||||
|
||||
u.update(() -> {
|
||||
boolean hadCommand = false;
|
||||
UnitCommand shareCommand = null;
|
||||
|
||||
//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();
|
||||
|
||||
if(hadCommand){
|
||||
if(shareCommand != nextCommand){
|
||||
shareCommand = null;
|
||||
}
|
||||
}else{
|
||||
shareCommand = nextCommand;
|
||||
hadCommand = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentCommand[0] = shareCommand;
|
||||
|
||||
int size = control.input.selectedUnits.size;
|
||||
if(curCount[0] != size){
|
||||
curCount[0] = size;
|
||||
|
@ -25,4 +25,4 @@ org.gradle.caching=true
|
||||
#used for slow jitpack builds; TODO see if this actually works
|
||||
org.gradle.internal.http.socketTimeout=100000
|
||||
org.gradle.internal.http.connectionTimeout=100000
|
||||
archash=7d543096d5
|
||||
archash=1477681512
|
||||
|
Loading…
Reference in New Issue
Block a user