WIP command order system

This commit is contained in:
Anuken
2022-07-30 21:01:07 -04:00
parent a326e36bbe
commit 55edd53f84
11 changed files with 327 additions and 62 deletions

View 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);
}
}

View File

@@ -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();

View File

@@ -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();
}
}*/
}