Logic unit control

This commit is contained in:
Anuken
2020-10-05 15:42:37 -04:00
parent 8e49d73765
commit 7088ae89b3
37 changed files with 661 additions and 202 deletions

View File

@@ -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;
}
}

View File

@@ -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){

View File

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

View File

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

View File

@@ -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){

View File

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

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

View File

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

View File

@@ -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),