Merge branch 'master' into new-logic-parser
# Conflicts: # gradle.properties
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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", ""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public enum LUnitControl{
|
||||
idle,
|
||||
stop,
|
||||
move("x", "y"),
|
||||
approach("x", "y", "radius"),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user