Logic unit control
This commit is contained in:
@@ -26,6 +26,8 @@ public enum LAccess{
|
||||
shooting,
|
||||
team,
|
||||
type,
|
||||
flag,
|
||||
name,
|
||||
|
||||
//values with parameters are considered controllable
|
||||
enabled("to"), //"to" is standard for single parameter access
|
||||
@@ -34,21 +36,21 @@ public enum LAccess{
|
||||
|
||||
;
|
||||
|
||||
public final String[] parameters;
|
||||
public final String[] params;
|
||||
public final boolean isObj;
|
||||
|
||||
public static final LAccess[]
|
||||
all = values(),
|
||||
senseable = Seq.select(all, t -> t.parameters.length <= 1).toArray(LAccess.class),
|
||||
controls = Seq.select(all, t -> t.parameters.length > 0).toArray(LAccess.class);
|
||||
senseable = Seq.select(all, t -> t.params.length <= 1).toArray(LAccess.class),
|
||||
controls = Seq.select(all, t -> t.params.length > 0).toArray(LAccess.class);
|
||||
|
||||
LAccess(String... parameters){
|
||||
this.parameters = parameters;
|
||||
LAccess(String... params){
|
||||
this.params = params;
|
||||
isObj = false;
|
||||
}
|
||||
|
||||
LAccess(boolean obj, String... parameters){
|
||||
this.parameters = parameters;
|
||||
LAccess(boolean obj, String... params){
|
||||
this.params = params;
|
||||
isObj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,12 @@ public class LAssembler{
|
||||
LInstruction[] instructions;
|
||||
|
||||
public LAssembler(){
|
||||
//instruction counter
|
||||
putVar("@counter").value = 0;
|
||||
//unix timestamp
|
||||
putConst("@time", 0);
|
||||
//currently controlled unit
|
||||
putConst("@unit", null);
|
||||
|
||||
//add default constants
|
||||
putConst("false", 0);
|
||||
@@ -45,6 +49,10 @@ public class LAssembler{
|
||||
}
|
||||
}
|
||||
|
||||
for(UnitType type : Vars.content.units()){
|
||||
putConst("@" + type.name, type);
|
||||
}
|
||||
|
||||
//store sensor constants
|
||||
|
||||
for(LAccess sensor : LAccess.all){
|
||||
|
||||
@@ -7,7 +7,8 @@ public enum LCategory{
|
||||
blocks(Pal.accentBack),
|
||||
control(Color.cyan.cpy().shiftSaturation(-0.6f).mul(0.7f)),
|
||||
operations(Pal.place.cpy().shiftSaturation(-0.5f).mul(0.7f)),
|
||||
io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f));
|
||||
io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f)),
|
||||
units(Pal.bulletYellowBack.cpy().shiftSaturation(-0.3f).mul(0.8f));
|
||||
|
||||
public final Color color;
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.logic.LogicDisplay.*;
|
||||
import mindustry.world.blocks.logic.MemoryBlock.*;
|
||||
import mindustry.world.blocks.logic.MessageBlock.*;
|
||||
@@ -23,7 +26,8 @@ public class LExecutor{
|
||||
//special variables
|
||||
public static final int
|
||||
varCounter = 0,
|
||||
varTime = 1;
|
||||
varTime = 1,
|
||||
varUnit = 2;
|
||||
|
||||
public static final int
|
||||
maxGraphicsBuffer = 256,
|
||||
@@ -36,6 +40,7 @@ public class LExecutor{
|
||||
public LongSeq graphicsBuffer = new LongSeq();
|
||||
public StringBuilder textBuffer = new StringBuilder();
|
||||
public Building[] links = {};
|
||||
public Team team = Team.derelict;
|
||||
|
||||
public boolean initialized(){
|
||||
return instructions != null && vars != null && instructions.length > 0;
|
||||
@@ -102,6 +107,11 @@ public class LExecutor{
|
||||
return v.isobj ? v.objval != null ? 1 : 0 : v.numval;
|
||||
}
|
||||
|
||||
public float numf(int index){
|
||||
Var v = vars[index];
|
||||
return v.isobj ? v.objval != null ? 1 : 0 : (float)v.numval;
|
||||
}
|
||||
|
||||
public int numi(int index){
|
||||
return (int)num(index);
|
||||
}
|
||||
@@ -121,6 +131,12 @@ public class LExecutor{
|
||||
v.isobj = true;
|
||||
}
|
||||
|
||||
public void setconst(int index, Object value){
|
||||
Var v = vars[index];
|
||||
v.objval = value;
|
||||
v.isobj = true;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
public static class Var{
|
||||
@@ -142,6 +158,153 @@ public class LExecutor{
|
||||
void run(LExecutor exec);
|
||||
}
|
||||
|
||||
/** Binds the processor to a unit based on some filters. */
|
||||
public static class UnitBindI implements LInstruction{
|
||||
public int type;
|
||||
|
||||
//iteration index
|
||||
private int index;
|
||||
|
||||
public UnitBindI(int type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public UnitBindI(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
Object typeObj = exec.obj(type);
|
||||
UnitType type = typeObj instanceof UnitType t ? t : null;
|
||||
|
||||
Seq<Unit> seq = type == null ? exec.team.data().units : exec.team.data().unitCache(type);
|
||||
|
||||
if(seq != null && seq.any()){
|
||||
index %= seq.size;
|
||||
if(index < seq.size){
|
||||
//bind to the next unit
|
||||
exec.setconst(varUnit, seq.get(index));
|
||||
}
|
||||
index ++;
|
||||
}else{
|
||||
//no units of this type found
|
||||
exec.setconst(varUnit, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Controls the unit based on some parameters. */
|
||||
public static class UnitControlI implements LInstruction{
|
||||
public LUnitControl type = LUnitControl.move;
|
||||
public int p1, p2, p3, p4;
|
||||
|
||||
public UnitControlI(LUnitControl type, int p1, int p2, int p3, int p4){
|
||||
this.type = type;
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.p3 = p3;
|
||||
this.p4 = p4;
|
||||
}
|
||||
|
||||
public UnitControlI(){
|
||||
}
|
||||
|
||||
/** Checks is a unit is valid for logic AI control, and returns the controller. */
|
||||
@Nullable
|
||||
public static LogicAI checkLogicAI(LExecutor exec, Object unitObj){
|
||||
if(unitObj instanceof Unit unit && exec.obj(varUnit) == unit && unit.team == exec.team && !unit.isPlayer() && !(unit.controller() instanceof FormationAI)){
|
||||
if(!(unit.controller() instanceof LogicAI)){
|
||||
unit.controller(new LogicAI());
|
||||
|
||||
//clear old state
|
||||
if(unit instanceof Minerc miner){
|
||||
miner.mineTile(null);
|
||||
}
|
||||
|
||||
if(unit instanceof Builderc builder){
|
||||
builder.clearBuilding();
|
||||
}
|
||||
|
||||
return (LogicAI)unit.controller();
|
||||
}
|
||||
return (LogicAI)unit.controller();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
Object unitObj = exec.obj(varUnit);
|
||||
LogicAI ai = checkLogicAI(exec, unitObj);
|
||||
|
||||
//only control standard AI units
|
||||
if(unitObj instanceof Unit unit && ai != null){
|
||||
ai.controlTimer = LogicAI.logicControlTimeout;
|
||||
|
||||
switch(type){
|
||||
case move, stop, approach -> {
|
||||
ai.control = type;
|
||||
ai.moveX = exec.numf(p1);
|
||||
ai.moveY = exec.numf(p2);
|
||||
if(type == LUnitControl.approach){
|
||||
ai.moveRad = exec.numf(p3);
|
||||
}
|
||||
}
|
||||
case target -> {
|
||||
ai.posTarget.set(exec.numf(p1), exec.numf(p2));
|
||||
ai.aimControl = type;
|
||||
ai.mainTarget = null;
|
||||
ai.shoot = exec.bool(p3);
|
||||
}
|
||||
case targetp -> {
|
||||
ai.aimControl = type;
|
||||
ai.mainTarget = exec.obj(p1) instanceof Teamc t ? t : null;
|
||||
ai.shoot = exec.bool(p2);
|
||||
}
|
||||
case flag -> {
|
||||
unit.flag = exec.num(p1);
|
||||
}
|
||||
case mine -> {
|
||||
Tile tile = world.tileWorld(exec.numf(p1), exec.numf(p2));
|
||||
if(unit instanceof Minerc miner){
|
||||
miner.mineTile(tile);
|
||||
}
|
||||
}
|
||||
case itemDrop -> {
|
||||
if(ai.itemTimer > 0) return;
|
||||
|
||||
Building build = exec.building(p1);
|
||||
int amount = exec.numi(p2);
|
||||
int dropped = Math.min(unit.stack.amount, amount);
|
||||
if(build != null && dropped > 0 && unit.within(build, logicItemTransferRange)){
|
||||
int accepted = build.acceptStack(unit.item(), dropped, unit);
|
||||
if(accepted > 0){
|
||||
Call.transferItemTo(unit, unit.item(), accepted, unit.x, unit.y, build);
|
||||
ai.itemTimer = LogicAI.transferDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
case itemTake -> {
|
||||
if(ai.itemTimer > 0) return;
|
||||
|
||||
Building build = exec.building(p1);
|
||||
int amount = exec.numi(p3);
|
||||
|
||||
if(build != null && exec.obj(p2) instanceof Item item && unit.within(build, logicItemTransferRange)){
|
||||
int taken = Math.min(build.items.get(item), Math.min(amount, unit.maxAccepted(item)));
|
||||
|
||||
if(taken > 0){
|
||||
Call.takeItems(build, item, taken, unit);
|
||||
ai.itemTimer = LogicAI.transferDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Controls a building's state. */
|
||||
public static class ControlI implements LInstruction{
|
||||
public int target;
|
||||
@@ -311,16 +474,20 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
Building target = exec.building(radar);
|
||||
Object base = exec.obj(radar);
|
||||
|
||||
int sortDir = exec.bool(sortOrder) ? 1 : -1;
|
||||
LogicAI ai = null;
|
||||
|
||||
if(target instanceof Ranged){
|
||||
float range = ((Ranged)target).range();
|
||||
if(base instanceof Ranged r && r.team() == exec.team &&
|
||||
(base instanceof Building || (ai = UnitControlI.checkLogicAI(exec, base)) != null)){ //must be a building or a controllable unit
|
||||
float range = r.range();
|
||||
|
||||
Healthc targeted;
|
||||
|
||||
if(timer.get(30f)){
|
||||
//timers update on a fixed 30 tick interval
|
||||
//units update on a special timer per controller instance
|
||||
if((base instanceof Building && timer.get(30f)) || (ai != null && ai.checkTargetTimer(this))){
|
||||
//if any of the targets involve enemies
|
||||
boolean enemies = target1 == RadarTarget.enemy || target2 == RadarTarget.enemy || target3 == RadarTarget.enemy;
|
||||
|
||||
@@ -328,11 +495,11 @@ public class LExecutor{
|
||||
bestValue = 0;
|
||||
|
||||
if(enemies){
|
||||
for(Team enemy : state.teams.enemiesOf(target.team)){
|
||||
find(target, range, sortDir, enemy);
|
||||
for(Team enemy : state.teams.enemiesOf(r.team())){
|
||||
find(r, range, sortDir, enemy);
|
||||
}
|
||||
}else{
|
||||
find(target, range, sortDir, target.team);
|
||||
find(r, range, sortDir, r.team());
|
||||
}
|
||||
|
||||
lastTarget = targeted = best;
|
||||
@@ -346,14 +513,14 @@ public class LExecutor{
|
||||
}
|
||||
}
|
||||
|
||||
void find(Building b, float range, int sortDir, Team team){
|
||||
Units.nearby(team, b.x, b.y, range, u -> {
|
||||
void find(Ranged b, float range, int sortDir, Team team){
|
||||
Units.nearby(team, b.x(), b.y(), range, u -> {
|
||||
if(!u.within(b, range)) return;
|
||||
|
||||
boolean valid =
|
||||
target1.func.get(b.team, u) &&
|
||||
target2.func.get(b.team, u) &&
|
||||
target3.func.get(b.team, u);
|
||||
target1.func.get(b.team(), u) &&
|
||||
target2.func.get(b.team(), u) &&
|
||||
target3.func.get(b.team(), u);
|
||||
|
||||
if(!valid) return;
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ public abstract class LStatement{
|
||||
return read.size == 0 ? null : read.first();
|
||||
}
|
||||
|
||||
public boolean hidden(){
|
||||
return false;
|
||||
}
|
||||
|
||||
//protected methods are only for internal UI layout utilities
|
||||
|
||||
protected Cell<TextField> field(Table table, String value, Cons<String> setter){
|
||||
@@ -38,9 +42,9 @@ public abstract class LStatement{
|
||||
.size(144f, 40f).pad(2f).color(table.color).addInputDialog();
|
||||
}
|
||||
|
||||
protected void fields(Table table, String desc, String value, Cons<String> setter){
|
||||
protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
|
||||
table.add(desc).padLeft(10).left();
|
||||
field(table, value, setter).width(85f).padRight(10).left();
|
||||
return field(table, value, setter).width(85f).padRight(10).left();
|
||||
}
|
||||
|
||||
protected void fields(Table table, String value, Cons<String> setter){
|
||||
|
||||
@@ -347,9 +347,9 @@ public class LStatements{
|
||||
//Q: why don't you just use arrays for this?
|
||||
//A: arrays aren't as easy to serialize so the code generator doesn't handle them
|
||||
int c = 0;
|
||||
for(int i = 0; i < type.parameters.length; i++){
|
||||
for(int i = 0; i < type.params.length; i++){
|
||||
|
||||
fields(table, type.parameters[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v);
|
||||
fields(table, type.params[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v);
|
||||
|
||||
if(++c % 2 == 0) row(table);
|
||||
}
|
||||
@@ -376,11 +376,13 @@ public class LStatements{
|
||||
public void build(Table table){
|
||||
table.defaults().left();
|
||||
|
||||
table.add(" from ");
|
||||
if(buildFrom()){
|
||||
table.add(" from ");
|
||||
|
||||
fields(table, radar, v -> radar = v);
|
||||
fields(table, radar, v -> radar = v);
|
||||
|
||||
row(table);
|
||||
row(table);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 3; i++){
|
||||
int fi = i;
|
||||
@@ -420,6 +422,10 @@ public class LStatements{
|
||||
fields(table, output, v -> output = v);
|
||||
}
|
||||
|
||||
public boolean buildFrom(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.blocks;
|
||||
@@ -694,4 +700,95 @@ public class LStatements{
|
||||
return LCategory.control;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("ubind")
|
||||
public static class UnitBindStatement extends LStatement{
|
||||
public String type = "@mono";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
table.add(" type ");
|
||||
|
||||
field(table, type, str -> type = str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.units;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new UnitBindI(builder.var(type));
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("ucontrol")
|
||||
public static class UnitControlStatement extends LStatement{
|
||||
public LUnitControl type = LUnitControl.move;
|
||||
public String p1 = "0", p2 = "0", p3 = "0", p4 = "0";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
rebuild(table);
|
||||
}
|
||||
|
||||
void rebuild(Table table){
|
||||
table.clearChildren();
|
||||
|
||||
table.left();
|
||||
|
||||
table.add(" ");
|
||||
|
||||
table.button(b -> {
|
||||
b.label(() -> type.name());
|
||||
b.clicked(() -> showSelect(b, LUnitControl.all, type, t -> {
|
||||
type = t;
|
||||
rebuild(table);
|
||||
}, 2, cell -> cell.size(120, 50)));
|
||||
}, Styles.logict, () -> {}).size(120, 40).color(table.color).left().padLeft(2);
|
||||
|
||||
row(table);
|
||||
|
||||
//Q: why don't you just use arrays for this?
|
||||
//A: arrays aren't as easy to serialize so the code generator doesn't handle them
|
||||
int c = 0;
|
||||
for(int i = 0; i < type.params.length; i++){
|
||||
|
||||
fields(table, type.params[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v).width(110f);
|
||||
|
||||
if(++c % 2 == 0) row(table);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.units;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new UnitControlI(type, builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("uradar")
|
||||
public static class UnitRadarStatement extends RadarStatement{
|
||||
|
||||
@Override
|
||||
public boolean buildFrom(){
|
||||
//do not build the "from" section
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.units;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new RadarI(target1, target2, target3, sort, LExecutor.varUnit, builder.var(sortOrder), builder.var(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
core/src/mindustry/logic/LUnitControl.java
Normal file
20
core/src/mindustry/logic/LUnitControl.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public enum LUnitControl{
|
||||
stop,
|
||||
move("x", "y"),
|
||||
approach("x", "y", "radius"),
|
||||
target("x", "y", "shoot"),
|
||||
targetp("unit", "shoot"),
|
||||
itemDrop("to", "amount"),
|
||||
itemTake("from", "item", "amount"),
|
||||
mine("x", "y"),
|
||||
flag("value");
|
||||
|
||||
public final String[] params;
|
||||
public static final LUnitControl[] all = values();
|
||||
|
||||
LUnitControl(String... params){
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class LogicDialog extends BaseDialog{
|
||||
int i = 0;
|
||||
for(Prov<LStatement> prov : LogicIO.allStatements){
|
||||
LStatement example = prov.get();
|
||||
if(example instanceof InvalidStatement) continue;
|
||||
if(example instanceof InvalidStatement || example.hidden()) continue;
|
||||
|
||||
TextButtonStyle style = new TextButtonStyle(Styles.cleart);
|
||||
style.fontColor = example.category().color;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
|
||||
public enum LogicOp{
|
||||
add("+", (a, b) -> a + b),
|
||||
@@ -9,8 +10,8 @@ public enum LogicOp{
|
||||
div("/", (a, b) -> a / b),
|
||||
idiv("//", (a, b) -> Math.floor(a / b)),
|
||||
mod("%", (a, b) -> a % b),
|
||||
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> a == b ? 1 : 0),
|
||||
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (a, b) -> a != b ? 1 : 0),
|
||||
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> Structs.eq(a, b) ? 1 : 0),
|
||||
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (a, b) -> !Structs.eq(a, b) ? 1 : 0),
|
||||
lessThan("<", (a, b) -> a < b ? 1 : 0),
|
||||
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
|
||||
greaterThan(">", (a, b) -> a > b ? 1 : 0),
|
||||
|
||||
Reference in New Issue
Block a user