mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-10-06 12:57:17 +03:00
WIP command order system
This commit is contained in:
parent
a326e36bbe
commit
55edd53f84
@ -311,6 +311,11 @@ open = Open
|
|||||||
customize = Customize Rules
|
customize = Customize Rules
|
||||||
cancel = Cancel
|
cancel = Cancel
|
||||||
command = Command
|
command = Command
|
||||||
|
command.mine = Mine
|
||||||
|
command.repair = Repair
|
||||||
|
command.rebuild = Rebuild
|
||||||
|
command.assist = Assist Player
|
||||||
|
command.move = Move
|
||||||
openlink = Open Link
|
openlink = Open Link
|
||||||
copylink = Copy Link
|
copylink = Copy Link
|
||||||
back = Back
|
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 class BuilderAI extends AIController{
|
||||||
public static float buildRadius = 1500, retreatDst = 110f, retreatDelay = Time.toSeconds * 2f;
|
public static float buildRadius = 1500, retreatDst = 110f, retreatDelay = Time.toSeconds * 2f;
|
||||||
|
|
||||||
|
public @Nullable Unit assistFollowing;
|
||||||
public @Nullable Unit following;
|
public @Nullable Unit following;
|
||||||
public @Nullable Teamc enemy;
|
public @Nullable Teamc enemy;
|
||||||
public @Nullable BlockPlan lastPlan;
|
public @Nullable BlockPlan lastPlan;
|
||||||
|
|
||||||
public float fleeRange = 370f;
|
public float fleeRange = 370f;
|
||||||
public boolean alwaysFlee;
|
public boolean alwaysFlee;
|
||||||
|
public boolean onlyAssist;
|
||||||
|
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
float retreatTimer;
|
float retreatTimer;
|
||||||
@ -41,6 +43,10 @@ public class BuilderAI extends AIController{
|
|||||||
|
|
||||||
unit.updateBuilding = true;
|
unit.updateBuilding = true;
|
||||||
|
|
||||||
|
if(assistFollowing != null && assistFollowing.activelyBuilding()){
|
||||||
|
following = assistFollowing;
|
||||||
|
}
|
||||||
|
|
||||||
if(following != null){
|
if(following != null){
|
||||||
retreatTimer = 0f;
|
retreatTimer = 0f;
|
||||||
//try to follow and mimic someone
|
//try to follow and mimic someone
|
||||||
@ -108,6 +114,10 @@ public class BuilderAI extends AIController{
|
|||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
|
if(assistFollowing != null){
|
||||||
|
moveTo(assistFollowing, assistFollowing.type.hitSize * 1.5f + 60f);
|
||||||
|
}
|
||||||
|
|
||||||
//follow someone and help them build
|
//follow someone and help them build
|
||||||
if(timer.get(timerTarget2, 60f)){
|
if(timer.get(timerTarget2, 60f)){
|
||||||
found = false;
|
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
|
//TODO this is bad, rebuild time should not depend on AI here
|
||||||
float rebuildTime = (unit.team.rules().rtsAi ? 12f : 2f) * 60f;
|
float rebuildTime = (unit.team.rules().rtsAi ? 12f : 2f) * 60f;
|
||||||
|
|
||||||
//find new plan
|
//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;
|
Queue<BlockPlan> blocks = unit.team.data().plans;
|
||||||
BlockPlan block = blocks.first();
|
BlockPlan block = blocks.first();
|
||||||
|
|
||||||
|
@ -12,20 +12,61 @@ import mindustry.gen.*;
|
|||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
|
||||||
public class CommandAI extends AIController{
|
public class CommandAI extends AIController{
|
||||||
private static final float localInterval = 40f;
|
protected 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 Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2();
|
||||||
|
|
||||||
public @Nullable Vec2 targetPos;
|
public @Nullable Vec2 targetPos;
|
||||||
public @Nullable Teamc attackTarget;
|
public @Nullable Teamc attackTarget;
|
||||||
|
|
||||||
private boolean stopAtTarget;
|
protected boolean stopAtTarget;
|
||||||
private Vec2 lastTargetPos;
|
protected Vec2 lastTargetPos;
|
||||||
private int pathId = -1;
|
protected int pathId = -1;
|
||||||
private Seq<Unit> local = new Seq<>(false);
|
protected Seq<Unit> local = new Seq<>(false);
|
||||||
private boolean flocked;
|
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
|
@Override
|
||||||
public void updateUnit(){
|
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();
|
updateVisuals();
|
||||||
updateTargeting();
|
updateTargeting();
|
||||||
|
|
||||||
@ -104,6 +145,7 @@ public class CommandAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(attackTarget == null){
|
if(attackTarget == null){
|
||||||
|
//TODO overshoot.
|
||||||
if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){
|
if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){
|
||||||
targetPos = null;
|
targetPos = null;
|
||||||
}else if(local.size > 1){
|
}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 cohesionScl = 0.3f;
|
||||||
public static float cohesionRad = 3f, separationRad = 1.1f, separationScl = 1f, flockMult = 0.5f;
|
public static float cohesionRad = 3f, separationRad = 1.1f, separationScl = 1f, flockMult = 0.5f;
|
||||||
|
|
||||||
//TODO ひどい
|
|
||||||
Vec2 calculateFlock(){
|
Vec2 calculateFlock(){
|
||||||
if(local.isEmpty()) return flockVec.setZero();
|
if(local.isEmpty()) return flockVec.setZero();
|
||||||
|
|
||||||
@ -177,47 +264,5 @@ public class CommandAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return flockVec;
|
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.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
|
import mindustry.ai.*;
|
||||||
import mindustry.ai.types.*;
|
import mindustry.ai.types.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
@ -1247,8 +1248,11 @@ public class UnitTypes{
|
|||||||
//region air support
|
//region air support
|
||||||
|
|
||||||
mono = new UnitType("mono"){{
|
mono = new UnitType("mono"){{
|
||||||
|
//there's no reason to command monos anywhere. it's just annoying.
|
||||||
controller = u -> new MinerAI();
|
controller = u -> new MinerAI();
|
||||||
|
|
||||||
|
defaultCommand = UnitCommand.mineCommand;
|
||||||
|
|
||||||
flying = true;
|
flying = true;
|
||||||
drag = 0.06f;
|
drag = 0.06f;
|
||||||
accel = 0.12f;
|
accel = 0.12f;
|
||||||
@ -1266,7 +1270,7 @@ public class UnitTypes{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
poly = new UnitType("poly"){{
|
poly = new UnitType("poly"){{
|
||||||
controller = u -> new BuilderAI();
|
defaultCommand = UnitCommand.rebuildCommand;
|
||||||
|
|
||||||
flying = true;
|
flying = true;
|
||||||
drag = 0.05f;
|
drag = 0.05f;
|
||||||
@ -1320,7 +1324,7 @@ public class UnitTypes{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
mega = new UnitType("mega"){{
|
mega = new UnitType("mega"){{
|
||||||
controller = u -> new RepairAI();
|
defaultCommand = UnitCommand.repairCommand;
|
||||||
|
|
||||||
mineTier = 3;
|
mineTier = 3;
|
||||||
mineSpeed = 4f;
|
mineSpeed = 4f;
|
||||||
|
@ -82,6 +82,8 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
|||||||
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
|
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
|
||||||
|
|
||||||
buildCounter += Time.delta;
|
buildCounter += Time.delta;
|
||||||
|
if(Float.isNaN(buildCounter) || Float.isInfinite(buildCounter)) buildCounter = 0f;
|
||||||
|
buildCounter = Math.min(buildCounter, 10f);
|
||||||
|
|
||||||
while(buildCounter >= 1){
|
while(buildCounter >= 1){
|
||||||
buildCounter -= 1f;
|
buildCounter -= 1f;
|
||||||
|
@ -14,6 +14,7 @@ import arc.scene.ui.layout.*;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
|
import mindustry.ai.*;
|
||||||
import mindustry.ai.types.*;
|
import mindustry.ai.types.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
@ -228,6 +229,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
for(int id : unitIds){
|
for(int id : unitIds){
|
||||||
Unit unit = Groups.unit.getByID(id);
|
Unit unit = Groups.unit.getByID(id);
|
||||||
if(unit != null && unit.team == player.team() && unit.controller() instanceof CommandAI ai){
|
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()){
|
if(teamTarget != null && teamTarget.team() != player.team()){
|
||||||
ai.commandTarget(teamTarget);
|
ai.commandTarget(teamTarget);
|
||||||
}else if(posTarget != null){
|
}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)
|
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||||
public static void commandBuilding(Player player, Building build, Vec2 target){
|
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;
|
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){
|
for(Unit unit : selectedUnits){
|
||||||
CommandAI ai = unit.command();
|
CommandAI ai = unit.command();
|
||||||
//draw target line
|
//draw target line
|
||||||
if(ai.targetPos != null){
|
if(ai.targetPos != null && ai.command == UnitCommand.moveCommand){
|
||||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||||
Drawf.limitLine(unit, lineDest, unit.hitSize / 2f, 3.5f);
|
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);
|
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);
|
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import arc.math.geom.*;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import arc.util.io.*;
|
import arc.util.io.*;
|
||||||
|
import mindustry.ai.*;
|
||||||
import mindustry.ai.types.*;
|
import mindustry.ai.types.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.TechTree.*;
|
import mindustry.content.TechTree.*;
|
||||||
@ -283,6 +284,14 @@ public class TypeIO{
|
|||||||
return Nulls.unit;
|
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){
|
public static void writeEntity(Writes write, Entityc entity){
|
||||||
write.i(entity == null ? -1 : entity.id());
|
write.i(entity == null ? -1 : entity.id());
|
||||||
}
|
}
|
||||||
@ -441,7 +450,7 @@ public class TypeIO{
|
|||||||
write.b(3);
|
write.b(3);
|
||||||
write.i(logic.controller.pos());
|
write.i(logic.controller.pos());
|
||||||
}else if(control instanceof CommandAI ai){
|
}else if(control instanceof CommandAI ai){
|
||||||
write.b(4);
|
write.b(6);
|
||||||
write.bool(ai.attackTarget != null);
|
write.bool(ai.attackTarget != null);
|
||||||
write.bool(ai.targetPos != null);
|
write.bool(ai.targetPos != null);
|
||||||
|
|
||||||
@ -457,6 +466,7 @@ public class TypeIO{
|
|||||||
write.i(((Unit)ai.attackTarget).id);
|
write.i(((Unit)ai.attackTarget).id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
write.b(ai.command == null ? -1 : ai.command.id);
|
||||||
}else if(control instanceof AssemblerAI){ //hate
|
}else if(control instanceof AssemblerAI){ //hate
|
||||||
write.b(5);
|
write.b(5);
|
||||||
}else{
|
}else{
|
||||||
@ -488,7 +498,8 @@ public class TypeIO{
|
|||||||
out.controller = world.build(pos);
|
out.controller = world.build(pos);
|
||||||
return out;
|
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();
|
CommandAI ai = prev instanceof CommandAI pai ? pai : new CommandAI();
|
||||||
|
|
||||||
boolean hasAttack = read.bool(), hasPos = read.bool();
|
boolean hasAttack = read.bool(), hasPos = read.bool();
|
||||||
@ -511,6 +522,11 @@ public class TypeIO{
|
|||||||
ai.attackTarget = null;
|
ai.attackTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(type == 6){
|
||||||
|
byte id = read.b();
|
||||||
|
ai.command = id < 0 ? null : UnitCommand.all.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
return ai;
|
return ai;
|
||||||
}else if(type == 5){
|
}else if(type == 5){
|
||||||
//augh
|
//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. */
|
/** 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};
|
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 */
|
/** color for outline generated around sprites */
|
||||||
public Color outlineColor = Pal.darkerMetal;
|
public Color outlineColor = Pal.darkerMetal;
|
||||||
/** thickness for sprite outline */
|
/** thickness for sprite outline */
|
||||||
@ -772,6 +777,33 @@ public class UnitType extends UnlockableContent{
|
|||||||
|
|
||||||
canAttack = weapons.contains(w -> !w.noAttack);
|
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
|
//dynamically create ammo capacity based on firing rate
|
||||||
if(ammoCapacity < 0){
|
if(ammoCapacity < 0){
|
||||||
float shotsPerSecond = weapons.sumf(w -> w.useAmmo ? 60f / w.reload : 0f);
|
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.scene.ui.layout.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
|
import mindustry.*;
|
||||||
|
import mindustry.ai.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.core.*;
|
import mindustry.core.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
@ -422,6 +424,8 @@ public class PlacementFragment{
|
|||||||
commandTable.table(u -> {
|
commandTable.table(u -> {
|
||||||
u.left();
|
u.left();
|
||||||
int[] curCount = {0};
|
int[] curCount = {0};
|
||||||
|
UnitCommand[] currentCommand = {null};
|
||||||
|
var commands = new Seq<UnitCommand>();
|
||||||
|
|
||||||
Runnable rebuildCommand = () -> {
|
Runnable rebuildCommand = () -> {
|
||||||
u.clearChildren();
|
u.clearChildren();
|
||||||
@ -431,12 +435,17 @@ public class PlacementFragment{
|
|||||||
for(var unit : units){
|
for(var unit : units){
|
||||||
counts[unit.type.id] ++;
|
counts[unit.type.id] ++;
|
||||||
}
|
}
|
||||||
|
commands.clear();
|
||||||
|
boolean firstCommand = false;
|
||||||
|
Table unitlist = u.table().growX().left().get();
|
||||||
|
unitlist.left();
|
||||||
|
|
||||||
int col = 0;
|
int col = 0;
|
||||||
for(int i = 0; i < counts.length; i++){
|
for(int i = 0; i < counts.length; i++){
|
||||||
if(counts[i] > 0){
|
if(counts[i] > 0){
|
||||||
var type = content.unit(i);
|
var type = content.unit(i);
|
||||||
u.add(new ItemImage(type.uiIcon, counts[i])).tooltip(type.localizedName).pad(4).with(b -> {
|
unitlist.add(new ItemImage(type.uiIcon, counts[i])).tooltip(type.localizedName).pad(4).with(b -> {
|
||||||
ClickListener listener = new ClickListener();
|
var listener = new ClickListener();
|
||||||
|
|
||||||
//left click -> select
|
//left click -> select
|
||||||
b.clicked(KeyCode.mouseLeft, () -> control.input.selectedUnits.removeAll(unit -> unit.type != type));
|
b.clicked(KeyCode.mouseLeft, () -> control.input.selectedUnits.removeAll(unit -> unit.type != type));
|
||||||
@ -450,16 +459,62 @@ public class PlacementFragment{
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(++col % 7 == 0){
|
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{
|
}else{
|
||||||
u.add("[no units]").color(Color.lightGray).growX().center().labelAlign(Align.center).pad(6);
|
u.add("[no units]").color(Color.lightGray).growX().center().labelAlign(Align.center).pad(6);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
u.update(() -> {
|
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;
|
int size = control.input.selectedUnits.size;
|
||||||
if(curCount[0] != size){
|
if(curCount[0] != size){
|
||||||
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
|
#used for slow jitpack builds; TODO see if this actually works
|
||||||
org.gradle.internal.http.socketTimeout=100000
|
org.gradle.internal.http.socketTimeout=100000
|
||||||
org.gradle.internal.http.connectionTimeout=100000
|
org.gradle.internal.http.connectionTimeout=100000
|
||||||
archash=7d543096d5
|
archash=1477681512
|
||||||
|
Loading…
Reference in New Issue
Block a user