Allow controlling/selecting units with LogicAI

This commit is contained in:
Anuken
2025-04-21 14:23:01 -04:00
parent f1f610079d
commit 847ec1813e
6 changed files with 100 additions and 76 deletions

View File

@@ -473,6 +473,12 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return controller instanceof AIController;
}
/** @return whether the unit *can* be commanded, even if its controller is not currently CommandAI. */
public boolean allowCommand(){
return controller instanceof CommandAI || (controller instanceof LogicAI && type.allowChangeCommands);
}
/** @return whether the unit has a CommandAI controller */
public boolean isCommandable(){
return controller instanceof CommandAI;
}

View File

@@ -288,14 +288,14 @@ public class DesktopInput extends InputHandler{
}
//validate commanding units
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid() || u.team != player.team());
selectedUnits.removeAll(u -> !u.allowCommand() || !u.isValid() || u.team != player.team());
if(commandMode && !scene.hasField() && !scene.hasDialog()){
if(input.keyTap(Binding.select_all_units)){
selectedUnits.clear();
commandBuildings.clear();
for(var unit : player.team().data().units){
if(unit.isCommandable()){
if(unit.allowCommand()){
selectedUnits.add(unit);
}
}

View File

@@ -261,43 +261,50 @@ 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){
if(unit != null && unit.team == player.team()){
//implicitly order it to move
if(ai.command == null || ai.command.switchToMove){
ai.command(UnitCommand.moveCommand);
if(unit.controller() instanceof LogicAI){
//reset to commandAI if applicable
unit.resetController();
}
if(teamTarget != null && teamTarget.team() != player.team() &&
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
anyCommandedTarget = true;
if(queueCommand){
ai.commandQueue(teamTarget);
}else{
ai.commandQueue.clear();
ai.commandTarget(teamTarget);
if(unit.controller() instanceof CommandAI ai){
//implicitly order it to move
if(ai.command == null || ai.command.switchToMove){
ai.command(UnitCommand.moveCommand);
}
}else if(posTarget != null){
if(queueCommand){
ai.commandQueue(posTarget);
}else{
ai.commandQueue.clear();
ai.commandPosition(posTarget);
if(teamTarget != null && teamTarget.team() != player.team() &&
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
anyCommandedTarget = true;
if(queueCommand){
ai.commandQueue(teamTarget);
}else{
ai.commandQueue.clear();
ai.commandTarget(teamTarget);
}
}else if(posTarget != null){
if(queueCommand){
ai.commandQueue(posTarget);
}else{
ai.commandQueue.clear();
ai.commandPosition(posTarget);
}
}
}
unit.lastCommanded = player.coloredName();
if(ai.commandQueue.size <= 0){
ai.group = null;
}
unit.lastCommanded = player.coloredName();
if(ai.commandQueue.size <= 0){
ai.group = null;
}
//remove when other player command
if(!headless && player != Vars.player){
control.input.selectedUnits.remove(unit);
}
//remove when other player command
if(!headless && player != Vars.player){
control.input.selectedUnits.remove(unit);
}
toAdd.add(unit);
toAdd.add(unit);
}
}
}
@@ -1093,38 +1100,42 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public void drawCommanded(boolean flying){
float lineLimit = 6.5f;
Color color = Pal.accent;
int sides = 6;
float alpha = 0.5f;
if(commandMode){
//happens sometimes
selectedUnits.removeAll(u -> !u.isCommandable());
selectedUnits.removeAll(u -> !u.allowCommand());
//draw command overlay UI
for(Unit unit : selectedUnits){
CommandAI ai = unit.command();
Position lastPos = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
Color color = unit.controller() instanceof LogicAI ? Team.malis.color : Pal.accent;
if(flying && ai.attackTarget != null && ai.currentCommand().drawTarget){
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
}
Position lastPos = null;
if(unit.isFlying() != flying) continue;
if(unit.controller() instanceof CommandAI ai){
lastPos = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
//draw target line
if(ai.targetPos != null && ai.currentCommand().drawTarget){
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
Drawf.limitLine(unit, lineDest, unit.hitSize / unitSelectRadScl + 1f, lineLimit, color.write(Tmp.c1).a(alpha));
if(flying && ai.attackTarget != null && ai.currentCommand().drawTarget){
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
}
if(ai.attackTarget == null){
Drawf.square(lineDest.getX(), lineDest.getY(), 3.5f, color.write(Tmp.c1).a(alpha));
if(unit.isFlying() != flying) continue;
if(ai.currentCommand() == UnitCommand.enterPayloadCommand){
var build = world.buildWorld(lineDest.getX(), lineDest.getY());
if(build != null && build.block.acceptsUnitPayloads && build.team == unit.team){
Drawf.selected(build, color);
//draw target line
if(ai.targetPos != null && ai.currentCommand().drawTarget){
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
Drawf.limitLine(unit, lineDest, unit.hitSize / unitSelectRadScl + 1f, lineLimit, color.write(Tmp.c1).a(alpha));
if(ai.attackTarget == null){
Drawf.square(lineDest.getX(), lineDest.getY(), 3.5f, color.write(Tmp.c1).a(alpha));
if(ai.currentCommand() == UnitCommand.enterPayloadCommand){
var build = world.buildWorld(lineDest.getX(), lineDest.getY());
if(build != null && build.block.acceptsUnitPayloads && build.team == unit.team){
Drawf.selected(build, color);
}
}
}
}
@@ -1148,42 +1159,43 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//Lines.poly(unit.x, unit.y, sides, rad + 1.5f);
Draw.reset();
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, lineLimit, lineLimit, color.write(Tmp.c1).a(alpha));
lastPos = next;
if(unit.controller() instanceof CommandAI ai){
//draw command queue
if(ai.currentCommand().drawTarget && ai.commandQueue.size > 0){
for(var next : ai.commandQueue){
Drawf.limitLine(lastPos, next, lineLimit, lineLimit, color.write(Tmp.c1).a(alpha));
lastPos = next;
if(next instanceof Vec2 vec){
Drawf.square(vec.x, vec.y, 3.5f, color.write(Tmp.c1).a(alpha));
}else{
Drawf.target(next.getX(), next.getY(), 6f, Pal.remove);
if(next instanceof Vec2 vec){
Drawf.square(vec.x, vec.y, 3.5f, color.write(Tmp.c1).a(alpha));
}else{
Drawf.target(next.getX(), next.getY(), 6f, Pal.remove);
}
}
}
}
if(ai.targetPos != null && ai.currentCommand() == UnitCommand.loopPayloadCommand && unit instanceof Payloadc pay){
Draw.color(color, 0.4f + Mathf.absin(5f, 0.5f));
TextureRegion region = pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
float offset = 11f;
float size = 8f;
Draw.rect(region, ai.targetPos.x, ai.targetPos.y + offset, size, size / region.ratio());
if(ai.targetPos != null && ai.currentCommand() == UnitCommand.loopPayloadCommand && unit instanceof Payloadc pay){
Draw.color(color, 0.4f + Mathf.absin(5f, 0.5f));
TextureRegion region = pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
float offset = 11f;
float size = 8f;
Draw.rect(region, ai.targetPos.x, ai.targetPos.y + offset, size, size / region.ratio());
if(ai.commandQueue.size > 0){
region = !pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
Draw.rect(region, ai.commandQueue.first().getX(), ai.commandQueue.first().getY() + offset, size, size / region.ratio());
if(ai.commandQueue.size > 0){
region = !pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
Draw.rect(region, ai.commandQueue.first().getX(), ai.commandQueue.first().getY() + offset, size, size / region.ratio());
}
Draw.color();
}
Draw.color();
}
}
if(flying){
Color color = Pal.accent;
for(var commandBuild : commandBuildings){
if(commandBuild != null){
Drawf.square(commandBuild.x, commandBuild.y, commandBuild.hitSize() / 1.4f + 1f);
@@ -1897,7 +1909,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
tmpUnits.clear();
float rad = 4f;
tree.intersect(x - rad/2f, y - rad/2f, rad, rad, tmpUnits);
return tmpUnits.min(u -> u.isCommandable(), u -> u.dst(x, y) - u.hitSize/2f);
return tmpUnits.min(u -> u.allowCommand(), u -> u.dst(x, y) - u.hitSize/2f);
}
public @Nullable Unit selectedEnemyUnit(float x, float y){
@@ -1919,7 +1931,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
tmpUnits.clear();
float rad = 4f;
tree.intersect(Tmp.r1.set(x - rad/2f, y - rad/2f, rad*2f + w, rad*2f + h).normalize(), tmpUnits);
tmpUnits.removeAll(u -> !u.isCommandable() || !predicate.get(u));
tmpUnits.removeAll(u -> !u.allowCommand() || !predicate.get(u));
return tmpUnits;
}

View File

@@ -770,7 +770,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//validate commanding units
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid() || u.team != player.team());
selectedUnits.removeAll(u -> !u.allowCommand() || !u.isValid() || u.team != player.team());
if(!commandMode){
commandBuildings.clear();

View File

@@ -109,6 +109,13 @@ public class LandingPad extends Block{
addBar("cooldown", (LandingPadBuild entity) -> new Bar("bar.cooldown", Pal.lightOrange, () -> entity.cooldown));
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.cooldownTime, (cooldownTime+arrivalDuration)/60f, StatUnit.seconds);
}
@Override
public boolean outputsItems(){
return true;