WIP command queueing
This commit is contained in:
@@ -1158,6 +1158,7 @@ keybind.mouse_move.name = Follow Mouse
|
|||||||
keybind.pan.name = Pan View
|
keybind.pan.name = Pan View
|
||||||
keybind.boost.name = Boost
|
keybind.boost.name = Boost
|
||||||
keybind.command_mode.name = Command Mode
|
keybind.command_mode.name = Command Mode
|
||||||
|
keybind.command_queue.name = Unit Command Queue
|
||||||
keybind.rebuild_select.name = Rebuild Region
|
keybind.rebuild_select.name = Rebuild Region
|
||||||
keybind.schematic_select.name = Select Region
|
keybind.schematic_select.name = Select Region
|
||||||
keybind.schematic_menu.name = Schematic Menu
|
keybind.schematic_menu.name = Schematic Menu
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import mindustry.gen.*;
|
|||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
|
||||||
public class CommandAI extends AIController{
|
public class CommandAI extends AIController{
|
||||||
|
protected static final int maxCommandQueueSize = 64;
|
||||||
protected static final float localInterval = 40f;
|
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();
|
protected static final Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2();
|
||||||
protected static final boolean[] noFound = {false};
|
protected static final boolean[] noFound = {false};
|
||||||
|
|
||||||
|
public Seq<Position> commandQueue = new Seq<>(5);
|
||||||
public @Nullable Vec2 targetPos;
|
public @Nullable Vec2 targetPos;
|
||||||
public @Nullable Teamc attackTarget;
|
public @Nullable Teamc attackTarget;
|
||||||
/** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */
|
/** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */
|
||||||
@@ -61,6 +63,11 @@ public class CommandAI extends AIController{
|
|||||||
@Override
|
@Override
|
||||||
public void updateUnit(){
|
public void updateUnit(){
|
||||||
|
|
||||||
|
//remove invalid targets
|
||||||
|
if(commandQueue.any()){
|
||||||
|
commandQueue.removeAll(e -> e instanceof Healthc h && !h.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
//assign defaults
|
//assign defaults
|
||||||
if(command == null && unit.type.commands.length > 0){
|
if(command == null && unit.type.commands.length > 0){
|
||||||
command = unit.type.defaultCommand == null ? unit.type.commands[0] : unit.type.defaultCommand;
|
command = unit.type.defaultCommand == null ? unit.type.commands[0] : unit.type.defaultCommand;
|
||||||
@@ -114,6 +121,11 @@ public class CommandAI extends AIController{
|
|||||||
targetPos = null;
|
targetPos = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//move on to the next target
|
||||||
|
if(attackTarget == null && targetPos == null){
|
||||||
|
finishPath();
|
||||||
|
}
|
||||||
|
|
||||||
if(targetPos != null){
|
if(targetPos != null){
|
||||||
if(timer.get(timerTarget3, localInterval) || !flocked){
|
if(timer.get(timerTarget3, localInterval) || !flocked){
|
||||||
if(!flocked){
|
if(!flocked){
|
||||||
@@ -160,7 +172,7 @@ public class CommandAI extends AIController{
|
|||||||
unreachableBuildings.addUnique(build.pos());
|
unreachableBuildings.addUnique(build.pos());
|
||||||
}
|
}
|
||||||
attackTarget = null;
|
attackTarget = null;
|
||||||
targetPos = null;
|
finishPath();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +205,7 @@ public class CommandAI extends AIController{
|
|||||||
|
|
||||||
if(attackTarget == null){
|
if(attackTarget == null){
|
||||||
if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){
|
if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){
|
||||||
targetPos = null;
|
finishPath();
|
||||||
}else if(local.size > 1){
|
}else if(local.size > 1){
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for(var near : local){
|
for(var near : local){
|
||||||
@@ -205,13 +217,13 @@ public class CommandAI extends AIController{
|
|||||||
|
|
||||||
//others have arrived at destination, so this one will too
|
//others have arrived at destination, so this one will too
|
||||||
if(count >= Math.max(3, local.size / 2)){
|
if(count >= Math.max(3, local.size / 2)){
|
||||||
targetPos = null;
|
finishPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stopWhenInRange && targetPos != null && unit.within(targetPos, engageRange * 0.9f)){
|
if(stopWhenInRange && targetPos != null && unit.within(targetPos, engageRange * 0.9f)){
|
||||||
targetPos = null;
|
finishPath();
|
||||||
stopWhenInRange = false;
|
stopWhenInRange = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +232,30 @@ public class CommandAI extends AIController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void finishPath(){
|
||||||
|
targetPos = null;
|
||||||
|
if(commandQueue.size > 0){
|
||||||
|
var next = commandQueue.remove(0);
|
||||||
|
if(next instanceof Teamc target){
|
||||||
|
commandTarget(target, this.stopAtTarget);
|
||||||
|
}else if(next instanceof Vec2 position){
|
||||||
|
commandPosition(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandQueue(Position location){
|
||||||
|
if(targetPos == null && attackTarget == null){
|
||||||
|
if(location instanceof Teamc target){
|
||||||
|
commandTarget(target, this.stopAtTarget);
|
||||||
|
}else if(location instanceof Vec2 position){
|
||||||
|
commandPosition(position);
|
||||||
|
}
|
||||||
|
}else if(commandQueue.size < maxCommandQueueSize && !commandQueue.contains(location)){
|
||||||
|
commandQueue.add(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hit(Bullet bullet){
|
public void hit(Bullet bullet){
|
||||||
if(unit.team.isAI() && bullet.owner instanceof Teamc teamc && teamc.team() != unit.team && attackTarget == null &&
|
if(unit.team.isAI() && bullet.owner instanceof Teamc teamc && teamc.team() != unit.team && attackTarget == null &&
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import static mindustry.Vars.*;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
abstract class StatusComp implements Posc, Flyingc{
|
abstract class StatusComp implements Posc, Flyingc{
|
||||||
private Seq<StatusEntry> statuses = new Seq<>();
|
private Seq<StatusEntry> statuses = new Seq<>(4);
|
||||||
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
|
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
|
||||||
|
|
||||||
//these are considered read-only
|
//these are considered read-only
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public enum Binding implements KeyBind{
|
|||||||
|
|
||||||
boost(KeyCode.shiftLeft),
|
boost(KeyCode.shiftLeft),
|
||||||
command_mode(KeyCode.shiftLeft),
|
command_mode(KeyCode.shiftLeft),
|
||||||
|
command_queue(KeyCode.mouseMiddle),
|
||||||
control(KeyCode.controlLeft),
|
control(KeyCode.controlLeft),
|
||||||
respawn(KeyCode.v),
|
respawn(KeyCode.v),
|
||||||
select(KeyCode.mouseLeft),
|
select(KeyCode.mouseLeft),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import arc.Graphics.Cursor.*;
|
|||||||
import arc.graphics.*;
|
import arc.graphics.*;
|
||||||
import arc.graphics.g2d.*;
|
import arc.graphics.g2d.*;
|
||||||
import arc.input.*;
|
import arc.input.*;
|
||||||
|
import arc.input.KeyCode.*;
|
||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.scene.*;
|
import arc.scene.*;
|
||||||
@@ -400,6 +401,10 @@ public class DesktopInput extends InputHandler{
|
|||||||
if(canAttack){
|
if(canAttack){
|
||||||
cursorType = ui.targetCursor;
|
cursorType = ui.targetCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(input.keyTap(Binding.command_queue) && keybinds.get(Binding.command_mode).key.type != KeyType.mouse){
|
||||||
|
commandTap(input.mouseX(), input.mouseY(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(getPlan(cursor.x, cursor.y) != null && mode == none){
|
if(getPlan(cursor.x, cursor.y) != null && mode == none){
|
||||||
@@ -721,6 +726,10 @@ public class DesktopInput extends InputHandler{
|
|||||||
commandTap(x, y);
|
commandTap(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(button == keybinds.get(Binding.command_queue).key){
|
||||||
|
commandTap(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
return super.touchDown(x, y, pointer, button);
|
return super.touchDown(x, y, pointer, button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||||
public static void commandUnits(Player player, int[] unitIds, @Nullable Building buildTarget, @Nullable Unit unitTarget, @Nullable Vec2 posTarget){
|
public static void commandUnits(Player player, int[] unitIds, @Nullable Building buildTarget, @Nullable Unit unitTarget, @Nullable Vec2 posTarget, boolean queueCommand){
|
||||||
if(player == null || unitIds == null) return;
|
if(player == null || unitIds == null) return;
|
||||||
|
|
||||||
//why did I ever think this was a good idea
|
//why did I ever think this was a good idea
|
||||||
@@ -240,9 +240,19 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
|
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
|
||||||
|
|
||||||
anyCommandedTarget = true;
|
anyCommandedTarget = true;
|
||||||
ai.commandTarget(teamTarget);
|
if(queueCommand){
|
||||||
|
ai.commandQueue(teamTarget);
|
||||||
|
}else{
|
||||||
|
ai.commandQueue.clear();
|
||||||
|
ai.commandTarget(teamTarget);
|
||||||
|
}
|
||||||
}else if(posTarget != null){
|
}else if(posTarget != null){
|
||||||
ai.commandPosition(posTarget);
|
if(queueCommand){
|
||||||
|
ai.commandQueue(posTarget);
|
||||||
|
}else{
|
||||||
|
ai.commandQueue.clear();
|
||||||
|
ai.commandPosition(posTarget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.lastCommanded = player.coloredName();
|
unit.lastCommanded = player.coloredName();
|
||||||
@@ -854,6 +864,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void commandTap(float screenX, float screenY){
|
public void commandTap(float screenX, float screenY){
|
||||||
|
commandTap(screenX, screenY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandTap(float screenX, float screenY, boolean queue){
|
||||||
if(commandMode){
|
if(commandMode){
|
||||||
//right click: move to position
|
//right click: move to position
|
||||||
|
|
||||||
@@ -882,10 +896,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
if(ids.length > maxChunkSize){
|
if(ids.length > maxChunkSize){
|
||||||
for(int i = 0; i < ids.length; i += maxChunkSize){
|
for(int i = 0; i < ids.length; i += maxChunkSize){
|
||||||
int[] data = Arrays.copyOfRange(ids, i, Math.min(i + maxChunkSize, ids.length));
|
int[] data = Arrays.copyOfRange(ids, i, Math.min(i + maxChunkSize, ids.length));
|
||||||
Call.commandUnits(player, data, attack instanceof Building b ? b : null, attack instanceof Unit u ? u : null, target);
|
Call.commandUnits(player, data, attack instanceof Building b ? b : null, attack instanceof Unit u ? u : null, target, queue);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Call.commandUnits(player, ids, attack instanceof Building b ? b : null, attack instanceof Unit u ? u : null, target);
|
Call.commandUnits(player, ids, attack instanceof Building b ? b : null, attack instanceof Unit u ? u : null, target, queue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -907,6 +921,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
//draw command overlay UI
|
//draw command overlay UI
|
||||||
for(Unit unit : selectedUnits){
|
for(Unit unit : selectedUnits){
|
||||||
CommandAI ai = unit.command();
|
CommandAI ai = unit.command();
|
||||||
|
Position lastPos = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||||
|
|
||||||
//draw target line
|
//draw target line
|
||||||
if(ai.targetPos != null && ai.currentCommand().drawTarget){
|
if(ai.targetPos != null && ai.currentCommand().drawTarget){
|
||||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||||
@@ -922,6 +938,24 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
if(ai.attackTarget != null && ai.currentCommand().drawTarget){
|
if(ai.attackTarget != null && ai.currentCommand().drawTarget){
|
||||||
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(lastPos == null){
|
||||||
|
lastPos = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//draw command queue
|
||||||
|
if(ai.currentCommand().drawTarget && ai.commandQueue.size > 0){
|
||||||
|
for(var next : ai.commandQueue){
|
||||||
|
Drawf.limitLine(lastPos, next, 3.5f, 3.5f);
|
||||||
|
lastPos = next;
|
||||||
|
|
||||||
|
if(next instanceof Vec2 vec){
|
||||||
|
Drawf.square(vec.x, vec.y, 3.5f);
|
||||||
|
}else{
|
||||||
|
Drawf.target(next.getX(), next.getY(), 6f, Pal.remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var commandBuild : commandBuildings){
|
for(var commandBuild : commandBuildings){
|
||||||
|
|||||||
@@ -472,7 +472,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(6);
|
write.b(7);
|
||||||
write.bool(ai.attackTarget != null);
|
write.bool(ai.attackTarget != null);
|
||||||
write.bool(ai.targetPos != null);
|
write.bool(ai.targetPos != null);
|
||||||
|
|
||||||
@@ -489,6 +489,24 @@ public class TypeIO{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
write.b(ai.command == null ? -1 : ai.command.id);
|
write.b(ai.command == null ? -1 : ai.command.id);
|
||||||
|
|
||||||
|
write.b(ai.commandQueue.size);
|
||||||
|
for(var pos : ai.commandQueue){
|
||||||
|
if(pos instanceof Building b){
|
||||||
|
write.b(0);
|
||||||
|
write.i(b.pos());
|
||||||
|
}else if(pos instanceof Unit u){
|
||||||
|
write.b(1);
|
||||||
|
write.i(u.id);
|
||||||
|
}else if(pos instanceof Vec2 v){
|
||||||
|
write.b(2);
|
||||||
|
write.f(v.x);
|
||||||
|
write.f(v.y);
|
||||||
|
}else{
|
||||||
|
//who put garbage in the command queue??
|
||||||
|
write.b(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
}else if(control instanceof AssemblerAI){ //hate
|
}else if(control instanceof AssemblerAI){ //hate
|
||||||
write.b(5);
|
write.b(5);
|
||||||
}else{
|
}else{
|
||||||
@@ -520,8 +538,8 @@ public class TypeIO{
|
|||||||
out.controller = world.build(pos);
|
out.controller = world.build(pos);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
//type 4 is the old CommandAI with no commandIndex, type 6 is the new one with the index as a single byte.
|
//type 4 is the old CommandAI with no commandIndex, type 6 is the new one with the index as a single byte, type 7 is the one with the command queue
|
||||||
}else if(type == 4 || type == 6){
|
}else if(type == 4 || type == 6 || type == 7){
|
||||||
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();
|
||||||
@@ -544,11 +562,34 @@ public class TypeIO{
|
|||||||
ai.attackTarget = null;
|
ai.attackTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type == 6){
|
if(type == 6 || type == 7){
|
||||||
byte id = read.b();
|
byte id = read.b();
|
||||||
ai.command = id < 0 ? null : UnitCommand.all.get(id);
|
ai.command = id < 0 ? null : UnitCommand.all.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//command queue only in type 7
|
||||||
|
if(type == 7){
|
||||||
|
ai.commandQueue.clear();
|
||||||
|
int length = read.ub();
|
||||||
|
for(int i = 0; i < length; i++){
|
||||||
|
int commandType = read.b();
|
||||||
|
switch(commandType){
|
||||||
|
case 0 -> {
|
||||||
|
var build = world.build(read.i());
|
||||||
|
if(build != null) ai.commandQueue.add(build);
|
||||||
|
}
|
||||||
|
case 1 -> {
|
||||||
|
var unit = Groups.unit.getByID(read.i());
|
||||||
|
if(unit != null) ai.commandQueue.add(unit);
|
||||||
|
}
|
||||||
|
case 2 -> {
|
||||||
|
ai.commandQueue.add(new Vec2(read.f(), read.f()));
|
||||||
|
}
|
||||||
|
//otherwise disregard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ai;
|
return ai;
|
||||||
}else if(type == 5){
|
}else if(type == 5){
|
||||||
//augh
|
//augh
|
||||||
|
|||||||
Reference in New Issue
Block a user