Merge branch 'master' into new-logic-parser

# Conflicts:
#	gradle.properties
This commit is contained in:
Anuken
2021-03-08 18:03:00 -05:00
305 changed files with 8539 additions and 5490 deletions

View File

@@ -1,12 +1,15 @@
package mindustry.logic;
import arc.util.*;
public enum ConditionOp{
equal("==", (a, b) -> Math.abs(a - b) < 0.000001, (a, b) -> a == b),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001, (a, b) -> a != b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001, Structs::eq),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001, (a, b) -> !Structs.eq(a, b)),
lessThan("<", (a, b) -> a < b),
lessThanEq("<=", (a, b) -> a <= b),
greaterThan(">", (a, b) -> a > b),
greaterThanEq(">=", (a, b) -> a >= b),
strictEqual("===", (a, b) -> false),
always("always", (a, b) -> true);
public static final ConditionOp[] all = values();

View File

@@ -9,6 +9,8 @@ import mindustry.world.*;
/** Stores global constants for logic processors. */
public class GlobalConstants{
public static final int ctrlProcessor = 1, ctrlPlayer = 2, ctrlFormation = 3;
private ObjectIntMap<String> namesToIds = new ObjectIntMap<>();
private Seq<Var> vars = new Seq<>(Var.class);
@@ -19,6 +21,12 @@ public class GlobalConstants{
put("true", 1);
put("null", null);
//special enums
put("@ctrlProcessor", ctrlProcessor);
put("@ctrlPlayer", ctrlPlayer);
put("@ctrlFormation", ctrlFormation);
//store base content
for(Item item : Vars.content.items()){

View File

@@ -21,12 +21,17 @@ public enum LAccess{
maxHealth,
heat,
efficiency,
timescale,
rotation,
x,
y,
shootX,
shootY,
size,
dead,
range,
shooting,
boosting,
mineX,
mineY,
mining,
@@ -34,6 +39,7 @@ public enum LAccess{
type,
flag,
controlled,
controller,
commanded,
name,
config,
@@ -44,7 +50,8 @@ public enum LAccess{
enabled("to"), //"to" is standard for single parameter access
shoot("x", "y", "shoot"),
shootp(true, "unit", "shoot"),
configure(true, 30, "to");
configure(true, 30, "to"),
color("r", "g", "b");
public final String[] params;
public final boolean isObj;

View File

@@ -13,6 +13,11 @@ public class LAssembler{
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
public static final int maxTokenLength = 36;
private static final StringMap opNameChanges = StringMap.of(
"atan2", "angle",
"dst", "len"
);
private int lastVar;
/** Maps names to variable IDs. */
public ObjectMap<String, BVar> vars = new ObjectMap<>();
@@ -28,6 +33,8 @@ public class LAssembler{
putConst("@unit", null);
//reference to self
putConst("@this", null);
//global tick
putConst("@tick", 0);
}
public static LAssembler assemble(String data, int maxInstructions){
@@ -61,9 +68,6 @@ public class LAssembler{
String[] lines = data.split("\n");
int index = 0;
for(String line : lines){
//comments
int commentIdx = line.indexOf('#');
if(commentIdx != -1) line = line.substring(0, commentIdx).trim();
if(line.isEmpty()) continue;
//remove trailing semicolons in case someone adds them in for no reason
if(line.endsWith(";")) line = line.substring(0, line.length() - 1);
@@ -74,6 +78,7 @@ public class LAssembler{
try{
String[] arr;
if(line.startsWith("#")) continue;
//yes, I am aware that this can be split with regex, but that's slow and even more incomprehensible
if(line.contains(" ")){
@@ -83,7 +88,9 @@ public class LAssembler{
for(int i = 0; i < line.length() + 1; i++){
char c = i == line.length() ? ' ' : line.charAt(i);
if(c == '"'){
if(c == '#' && !inString){
break;
}else if(c == '"'){
inString = !inString;
}else if(c == ' ' && !inString){
tokens.add(line.substring(lastIdx, Math.min(i, lastIdx + maxTokenLength)));
@@ -96,6 +103,9 @@ public class LAssembler{
arr = new String[]{line};
}
//nothing found
if(arr.length == 0) continue;
String type = arr[0];
//legacy stuff
@@ -122,6 +132,11 @@ public class LAssembler{
}
}
//fix up changed operaiton names
if(type.equals("op")){
arr[1] = opNameChanges.get(arr[1], arr[1]);
}
LStatement st = LogicIO.read(arr);
if(st != null){

View File

@@ -13,6 +13,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
@@ -29,10 +30,26 @@ public class LCanvas extends Table{
StatementElem hovered;
float targetWidth;
int jumpCount = 0;
Seq<Tooltip> tooltips = new Seq<>();
public LCanvas(){
canvas = this;
Core.scene.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
//hide tooltips on tap
for(var t : tooltips){
t.container.toFront();
}
Core.app.post(() -> {
tooltips.each(Tooltip::hide);
tooltips.clear();
});
return super.touchDown(event, x, y, pointer, button);
}
});
rebuild();
}
@@ -41,6 +58,43 @@ public class LCanvas extends Table{
return Core.graphics.getWidth() < Scl.scl(900f) * 1.2f;
}
public static void tooltip(Cell<?> cell, String key){
String lkey = key.toLowerCase().replace(" ", "");
if(Core.settings.getBool("logichints", true) && Core.bundle.has(lkey)){
var tip = new Tooltip(t -> t.background(Styles.black8).margin(4f).add("[lightgray]" + Core.bundle.get(lkey)).style(Styles.outlineLabel));
//mobile devices need long-press tooltips
if(Vars.mobile){
cell.get().addListener(new ElementGestureListener(20, 0.4f, 0.43f, 0.15f){
@Override
public boolean longPress(Element element, float x, float y){
tip.show(element, x, y);
canvas.tooltips.add(tip);
//prevent touch down for other listeners
for(var list : cell.get().getListeners()){
if(list instanceof ClickListener cl){
cl.cancel();
}
}
return true;
}
});
}else{
cell.get().addListener(tip);
}
}
}
public static void tooltip(Cell<?> cell, Enum<?> key){
String cl = key.getClass().getSimpleName().toLowerCase() + "." + key.name().toLowerCase();
if(Core.bundle.has(cl)){
tooltip(cell, cl);
}else{
tooltip(cell, "lenum." + key.name());
}
}
public void rebuild(){
targetWidth = useRows() ? 400f : 900f;
float s = pane != null ? pane.getScrollPercentY() : 0f;
@@ -283,13 +337,13 @@ public class LCanvas extends Table{
t.add().growX();
t.button(Icon.copy, Styles.logici, () -> {
}).padRight(6).get().tapped(this::copy);
}).size(24f).padRight(6).get().tapped(this::copy);
t.button(Icon.cancel, Styles.logici, () -> {
remove();
dragging = null;
statements.layout();
});
}).size(24f);
t.addListener(new InputListener(){
float lastx, lasty;

View File

@@ -1,5 +1,6 @@
package mindustry.logic;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
@@ -35,7 +36,8 @@ public class LExecutor{
varCounter = 0,
varTime = 1,
varUnit = 2,
varThis = 3;
varThis = 3,
varTick = 4;
public static final int
maxGraphicsBuffer = 256,
@@ -60,6 +62,7 @@ public class LExecutor{
public void runOnce(){
//set time
vars[varTime].numval = Time.millis();
vars[varTick].numval = Time.time;
//reset to start
if(vars[varCounter].numval >= instructions.length || vars[varCounter].numval < 0){
@@ -98,6 +101,10 @@ public class LExecutor{
//region utility
private static boolean invalid(double d){
return Double.isNaN(d) || Double.isInfinite(d);
}
public Var var(int index){
//global constants have variable IDs < 0, and they are fetched from the global constants object after being negated
return index < 0 ? constants.get(-index) : vars[index];
@@ -120,12 +127,12 @@ public class LExecutor{
public double num(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : v.numval;
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : v.numval;
}
public float numf(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : (float)v.numval;
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : (float)v.numval;
}
public int numi(int index){
@@ -139,9 +146,14 @@ public class LExecutor{
public void setnum(int index, double value){
Var v = var(index);
if(v.constant) return;
v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : value;
v.objval = null;
v.isobj = false;
if(invalid(value)){
v.objval = null;
v.isobj = true;
}else{
v.numval = value;
v.objval = null;
v.isobj = false;
}
}
public void setobj(int index, Object value){
@@ -326,17 +338,19 @@ public class LExecutor{
@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());
((LogicAI)unit.controller()).controller = exec.building(varThis);
if(unit.controller() instanceof LogicAI la){
return la;
}else{
var la = new LogicAI();
la.controller = exec.building(varThis);
unit.controller(la);
//clear old state
unit.mineTile = null;
unit.clearBuilding();
return (LogicAI)unit.controller();
return la;
}
return (LogicAI)unit.controller();
}
return null;
}
@@ -433,8 +447,8 @@ public class LExecutor{
}
}
case build -> {
if(unit.canBuild() && exec.obj(p3) instanceof Block block){
int x = World.toTile(x1), y = World.toTile(y1);
if(state.rules.logicUnitBuild && unit.canBuild() && exec.obj(p3) instanceof Block block){
int x = World.toTile(x1 - block.offset/tilesize), y = World.toTile(y1 - block.offset/tilesize);
int rot = exec.numi(p4);
//reset state of last request when necessary
@@ -444,12 +458,14 @@ public class LExecutor{
ai.plan.stuck = false;
}
var conf = exec.obj(p5);
ai.plan.set(x, y, rot, block);
ai.plan.config = exec.obj(p5) instanceof Content c ? c : null;
ai.plan.config = conf instanceof Content c ? c : conf instanceof Building b ? b : null;
unit.clearBuilding();
Tile tile = ai.plan.tile();
if(ai.plan.tile() != null){
if(tile != null && !(tile.block() == block && tile.build != null && tile.build.rotation == rot)){
unit.updateBuilding = true;
unit.addBuild(ai.plan);
}
@@ -569,7 +585,7 @@ public class LExecutor{
int address = exec.numi(position);
Building from = exec.building(target);
if(from instanceof MemoryBuild mem){
if(from instanceof MemoryBuild mem && from.team == exec.team){
exec.setnum(output, address < 0 || address >= mem.memory.length ? 0 : mem.memory[address]);
}
@@ -593,7 +609,7 @@ public class LExecutor{
int address = exec.numi(position);
Building from = exec.building(target);
if(from instanceof MemoryBuild mem){
if(from instanceof MemoryBuild mem && from.team == exec.team){
if(address >= 0 && address < mem.memory.length){
mem.memory[address] = exec.num(value);
@@ -620,23 +636,28 @@ public class LExecutor{
Object target = exec.obj(from);
Object sense = exec.obj(type);
//TODO should remote enemy buildings be senseable?
if(target == null && sense == LAccess.dead){
exec.setnum(to, 1);
return;
}
//note that remote units/buildings can be sensed as well
if(target instanceof Senseable se){
if(sense instanceof Content){
exec.setnum(to, se.sense(((Content)sense)));
}else if(sense instanceof LAccess){
Object objOut = se.senseObject((LAccess)sense);
if(sense instanceof Content co){
exec.setnum(to, se.sense(co));
}else if(sense instanceof LAccess la){
Object objOut = se.senseObject(la);
if(objOut == Senseable.noSensed){
//numeric output
exec.setnum(to, se.sense((LAccess)sense));
exec.setnum(to, se.sense(la));
}else{
//object output
exec.setobj(to, objOut);
}
}
}else{
exec.setnum(to, 0);
exec.setobj(to, null);
}
}
}
@@ -752,7 +773,7 @@ public class LExecutor{
v.objval = f.objval;
v.isobj = true;
}else{
v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval;
v.numval = invalid(f.numval) ? 0 : f.numval;
v.isobj = false;
}
}
@@ -774,14 +795,17 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
if(op.unary){
if(op == LogicOp.strictEqual){
Var v = exec.var(a), v2 = exec.var(b);
exec.setnum(dest, v.isobj == v2.isobj && ((v.isobj && v.objval == v2.objval) || (!v.isobj && v.numval == v2.numval)) ? 1 : 0);
}else if(op.unary){
exec.setnum(dest, op.function1.get(exec.num(a)));
}else{
Var va = exec.var(a);
Var vb = exec.var(b);
if(op.objFunction2 != null && va.isobj && vb.isobj){
//use object function if provided, and one of the variables is an object
//use object function if both are objects
exec.setnum(dest, op.objFunction2.get(exec.obj(a), exec.obj(b)));
}else{
//otherwise use the numeric function
@@ -857,8 +881,7 @@ public class LExecutor{
//graphics on headless servers are useless.
if(Vars.headless) return;
Building build = exec.building(target);
if(build instanceof LogicDisplayBuild d){
if(exec.building(target) instanceof LogicDisplayBuild d && d.team == exec.team){
if(d.commands.size + exec.graphicsBuffer.size < maxDisplayBuffer){
for(int i = 0; i < exec.graphicsBuffer.size; i++){
d.commands.addLast(exec.graphicsBuffer.items[i]);
@@ -889,6 +912,7 @@ public class LExecutor{
String strValue =
v.objval == null ? "null" :
v.objval instanceof String s ? s :
v.objval == Blocks.stoneWall ? "solid" : //special alias
v.objval instanceof MappableContent content ? content.name :
v.objval instanceof Content ? "[content]" :
v.objval instanceof Building build ? build.block.name :
@@ -920,8 +944,7 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
Building build = exec.building(target);
if(build instanceof MessageBuild d){
if(exec.building(target) instanceof MessageBuild d && d.team == exec.team){
d.message.setLength(0);
d.message.append(exec.textBuffer, 0, Math.min(exec.textBuffer.length(), maxTextBuffer));
@@ -952,8 +975,10 @@ public class LExecutor{
Var vb = exec.var(compare);
boolean cmp;
if(op.objFunction != null && (va.isobj || vb.isobj)){
//use object function if provided, and one of the variables is an object
if(op == ConditionOp.strictEqual){
cmp = va.isobj == vb.isobj && ((va.isobj && va.objval == vb.objval) || (!va.isobj && va.numval == vb.numval));
}else if(op.objFunction != null && va.isobj && vb.isobj){
//use object function if both are objects
cmp = op.objFunction.get(exec.obj(value), exec.obj(compare));
}else{
cmp = op.function.get(exec.num(value), exec.num(compare));
@@ -966,5 +991,37 @@ public class LExecutor{
}
}
public static class WaitI implements LInstruction{
public int value;
public float curTime;
public double wait;
public long frameId;
public WaitI(int value){
this.value = value;
}
public WaitI(){
}
@Override
public void run(LExecutor exec){
if(curTime >= exec.num(value)){
curTime = 0f;
}else{
//skip back to self.
exec.var(varCounter).numval --;
}
if(Core.graphics.getFrameId() != frameId){
curTime += Time.delta / 60f;
frameId = Core.graphics.getFrameId();
}
}
}
//endregion
}

View File

@@ -15,6 +15,8 @@ import mindustry.logic.LCanvas.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import static mindustry.logic.LCanvas.*;
/**
* A statement is an intermediate representation of an instruction, to be used mostly in UI.
* Contains all relevant variable information. */
@@ -38,13 +40,18 @@ public abstract class LStatement{
//protected methods are only for internal UI layout utilities
protected void param(Cell<Label> label){
String text = name() + "." + label.get().getText().toString().trim();
tooltip(label, text);
}
protected Cell<TextField> field(Table table, String value, Cons<String> setter){
return table.field(value, Styles.nodeField, setter)
.size(144f, 40f).pad(2f).color(table.color).maxTextLength(LAssembler.maxTokenLength).addInputDialog();
}
protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
table.add(desc).padLeft(10).left();
table.add(desc).padLeft(10).left().self(this::param);;
return field(table, value, setter).width(85f).padRight(10).left();
}
@@ -58,29 +65,40 @@ public abstract class LStatement{
}
}
protected <T> void showSelect(Button b, T[] values, T current, Cons<T> getter, int cols, Cons<Cell> sizer){
protected <T extends Enum<T>> void showSelect(Button b, T[] values, T current, Cons<T> getter, int cols, Cons<Cell> sizer){
showSelectTable(b, (t, hide) -> {
ButtonGroup<Button> group = new ButtonGroup<>();
int i = 0;
t.defaults().size(56f, 40f);
t.defaults().size(60f, 38f);
for(T p : values){
sizer.get(t.button(p.toString(), Styles.clearTogglet, () -> {
sizer.get(t.button(p.toString(), Styles.logicTogglet, () -> {
getter.get(p);
hide.run();
}).checked(current == p).group(group));
}).self(c -> tooltip(c, p)).checked(current == p).group(group));
if(++i % cols == 0) t.row();
}
});
}
protected <T> void showSelect(Button b, T[] values, T current, Cons<T> getter){
protected <T extends Enum<T>> void showSelect(Button b, T[] values, T current, Cons<T> getter){
showSelect(b, values, current, getter, 4, c -> {});
}
protected void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
Table t = new Table(Tex.button);
Table t = new Table(Tex.paneSolid){
@Override
public float getPrefHeight(){
return Math.min(super.getPrefHeight(), Core.graphics.getHeight());
}
@Override
public float getPrefWidth(){
return Math.min(super.getPrefWidth(), Core.graphics.getWidth());
}
};
t.margin(4);
//triggers events behind the element to simulate deselection
Element hitter = new Element();
@@ -110,14 +128,15 @@ public abstract class LStatement{
if(t.getWidth() > Core.scene.getWidth()) t.setWidth(Core.graphics.getWidth());
if(t.getHeight() > Core.scene.getHeight()) t.setHeight(Core.graphics.getHeight());
t.keepInStage();
t.invalidateHierarchy();
t.pack();
});
t.actions(Actions.alpha(0), Actions.fadeIn(0.3f, Interp.fade));
t.top().pane(inner -> {
inner.marginRight(24f);
inner.top();
hideCons.get(inner, hide);
}).top();
}).pad(0f).top().get().setScrollingDisabled(true, false);
t.pack();
}
@@ -139,4 +158,5 @@ public abstract class LStatement{
public String name(){
return Strings.insertSpaces(getClass().getSimpleName().replace("Statement", ""));
}
}

View File

@@ -15,6 +15,7 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import static mindustry.logic.LCanvas.*;
import static mindustry.world.blocks.logic.LogicDisplay.*;
public class LStatements{
@@ -355,7 +356,7 @@ public class LStatements{
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
table.add(" of ");
table.add(" of ").self(this::param);
field(table, target, v -> target = v);
@@ -394,7 +395,7 @@ public class LStatements{
table.defaults().left();
if(buildFrom()){
table.add(" from ");
table.add(" from ").self(this::param);
fields(table, radar, v -> radar = v);
@@ -405,7 +406,7 @@ public class LStatements{
int fi = i;
Prov<RadarTarget> get = () -> (fi == 0 ? target1 : fi == 1 ? target2 : target3);
table.add(i == 0 ? " target " : " and ");
table.add(i == 0 ? " target " : " and ").self(this::param);
table.button(b -> {
b.label(() -> get.get().name());
@@ -419,13 +420,13 @@ public class LStatements{
}
}
table.add(" order ");
table.add(" order ").self(this::param);
fields(table, sortOrder, v -> sortOrder = v);
table.row();
table.add(" sort ");
table.add(" sort ").self(this::param);
table.button(b -> {
b.label(() -> sort.name());
@@ -434,7 +435,7 @@ public class LStatements{
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
table.add(" output ");
table.add(" output ").self(this::param);
fields(table, output, v -> output = v);
}
@@ -511,7 +512,7 @@ public class LStatements{
i.button(sensor.name(), Styles.cleart, () -> {
stype("@" + sensor.name());
hide.run();
}).size(240f, 40f).row();
}).size(240f, 40f).self(c -> tooltip(c, sensor)).row();
}
})
};
@@ -531,14 +532,14 @@ public class LStatements{
t.parent.parent.pack();
t.parent.parent.invalidateHierarchy();
}).size(80f, 50f).growX().checked(selected == fi).group(group);
}).height(50f).growX().checked(selected == fi).group(group);
}
t.row();
t.add(stack).colspan(3).width(240f).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
table.add(" in ");
table.add(" in ").self(this::param);
field(table, from, str -> from = str);
}
@@ -602,28 +603,51 @@ public class LStatements{
table.add(" = ");
if(op.unary){
opButton(table);
opButton(table, table);
field(table, a, str -> a = str);
}else{
row(table);
field(table, a, str -> a = str);
//"function"-type operations have the name at the left and arguments on the right
if(op.func){
if(LCanvas.useRows()){
table.left();
table.row();
table.table(c -> {
c.color.set(color());
c.left();
funcs(c, table);
}).colspan(2).left();
}else{
funcs(table, table);
}
}else{
field(table, a, str -> a = str);
opButton(table);
opButton(table, table);
field(table, b, str -> b = str);
field(table, b, str -> b = str);
}
}
}
void opButton(Table table){
void funcs(Table table, Table parent){
opButton(table, parent);
field(table, a, str -> a = str);
field(table, b, str -> b = str);
}
void opButton(Table table, Table parent){
table.button(b -> {
b.label(() -> op.symbol);
b.clicked(() -> showSelect(b, LogicOp.all, op, o -> {
op = o;
rebuild(table);
rebuild(parent);
}));
}, Styles.logict, () -> {}).size(60f, 40f).pad(4f).color(table.color);
}, Styles.logict, () -> {}).size(64f, 40f).pad(4f).color(table.color);
}
@Override
@@ -637,6 +661,28 @@ public class LStatements{
}
}
//TODO untested
//@RegisterStatement("wait")
public static class WaitStatement extends LStatement{
public String value = "0.5";
@Override
public void build(Table table){
field(table, value, str -> value = str);
table.add(" sec");
}
@Override
public Color color(){
return Pal.logicOperations;
}
@Override
public LInstruction build(LAssembler builder){
return new WaitI(builder.var(value));
}
}
@RegisterStatement("end")
public static class EndStatement extends LStatement{
@Override
@@ -783,7 +829,11 @@ public class LStatements{
table.button(b -> {
b.label(() -> type.name());
b.clicked(() -> showSelect(b, LUnitControl.all, type, t -> {
type = t;
if(t == LUnitControl.build && !Vars.state.rules.logicUnitBuild){
Vars.ui.showInfo("@logic.nounitbuild");
}else{
type = t;
}
rebuild(table);
}, 2, cell -> cell.size(120, 50)));
}, Styles.logict, () -> {}).size(120, 40).color(table.color).left().padLeft(2);
@@ -819,6 +869,10 @@ public class LStatements{
@RegisterStatement("uradar")
public static class UnitRadarStatement extends RadarStatement{
public UnitRadarStatement(){
radar = "0";
}
@Override
public boolean buildFrom(){
//do not build the "from" section
@@ -851,7 +905,7 @@ public class LStatements{
void rebuild(Table table){
table.clearChildren();
table.add(" find ").left();
table.add(" find ").left().self(this::param);;
table.button(b -> {
b.label(() -> locate.name());
@@ -864,14 +918,14 @@ public class LStatements{
switch(locate){
case building -> {
row(table);
table.add(" type ").left();
table.add(" group ").left().self(this::param);;
table.button(b -> {
b.label(() -> flag.name());
b.clicked(() -> showSelect(b, BlockFlag.all, flag, t -> flag = t, 2, cell -> cell.size(110, 50)));
b.clicked(() -> showSelect(b, BlockFlag.allLogic, flag, t -> flag = t, 2, cell -> cell.size(110, 50)));
}, Styles.logict, () -> {}).size(110, 40).color(table.color).left().padLeft(2);
row(table);
table.add(" enemy ").left();
table.add(" enemy ").left().self(this::param);;
fields(table, enemy, str -> enemy = str);
@@ -879,7 +933,7 @@ public class LStatements{
}
case ore -> {
table.add(" ore ").left();
table.add(" ore ").left().self(this::param);
table.table(ts -> {
ts.color.set(table.color);
@@ -916,19 +970,19 @@ public class LStatements{
}
}
table.add(" outX ").left();
table.add(" outX ").left().self(this::param);
fields(table, outX, str -> outX = str);
table.add(" outY ").left();
table.add(" outY ").left().self(this::param);
fields(table, outY, str -> outY = str);
row(table);
table.add(" found ").left();
table.add(" found ").left().self(this::param);
fields(table, outFound, str -> outFound = str);
if(locate != LLocate.ore){
table.add(" building ").left();
table.add(" building ").left().self(this::param);
fields(table, outBuild, str -> outBuild = str);
}

View File

@@ -1,6 +1,7 @@
package mindustry.logic;
public enum LUnitControl{
idle,
stop,
move("x", "y"),
approach("x", "y", "radius"),

View File

@@ -10,6 +10,7 @@ import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
public class LogicDialog extends BaseDialog{
public LCanvas canvas;
@@ -35,6 +36,11 @@ public class LogicDialog extends BaseDialog{
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
t.row();
t.button("@schematic.copy.import", Icon.download, style, () -> {
dialog.hide();
@@ -44,11 +50,6 @@ public class LogicDialog extends BaseDialog{
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null);
t.row();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
});
});
@@ -72,7 +73,7 @@ public class LogicDialog extends BaseDialog{
t.button(example.name(), style, () -> {
canvas.add(prov.get());
dialog.hide();
}).size(140f, 50f);
}).size(140f, 50f).self(c -> tooltip(c, "lst." + example.name()));
if(++i % 2 == 0) t.row();
}
});

View File

@@ -19,6 +19,7 @@ public enum LogicOp{
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
greaterThan(">", (a, b) -> a > b ? 1 : 0),
greaterThanEq(">=", (a, b) -> a >= b ? 1 : 0),
strictEqual("===", (a, b) -> 0), //this lambda is not actually used
shl("<<", (a, b) -> (long)a << (long)b),
shr(">>", (a, b) -> (long)a >> (long)b),
@@ -27,11 +28,11 @@ public enum LogicOp{
xor("xor", (a, b) -> (long)a ^ (long)b),
not("flip", a -> ~(long)(a)),
max("max", Math::max),
min("min", Math::min),
atan2("atan2", (x, y) -> Mathf.atan2((float)x, (float)y) * Mathf.radDeg),
dst("dst", (x, y) -> Mathf.dst((float)x, (float)y)),
noise("noise", LExecutor.noise::rawNoise2D),
max("max", true, Math::max),
min("min", true, Math::min),
angle("angle", true, (x, y) -> Angles.angle((float)x, (float)y)),
len("len", true, (x, y) -> Mathf.dst((float)x, (float)y)),
noise("noise", true, LExecutor.noise::rawNoise2D),
abs("abs", a -> Math.abs(a)),
log("log", Math::log),
log10("log10", Math::log10),
@@ -48,19 +49,29 @@ public enum LogicOp{
public final OpObjLambda2 objFunction2;
public final OpLambda2 function2;
public final OpLambda1 function1;
public final boolean unary;
public final boolean unary, func;
public final String symbol;
LogicOp(String symbol, OpLambda2 function){
this(symbol, function, null);
}
LogicOp(String symbol, boolean func, OpLambda2 function){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
this.objFunction2 = null;
this.func = func;
}
LogicOp(String symbol, OpLambda2 function, OpObjLambda2 objFunction){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
this.objFunction2 = objFunction;
this.func = false;
}
LogicOp(String symbol, OpLambda1 function){
@@ -69,6 +80,7 @@ public enum LogicOp{
this.function2 = null;
this.unary = true;
this.objFunction2 = null;
this.func = false;
}
@Override