package mindustry.logic; import arc.struct.*; import arc.util.ArcAnnotate.*; import arc.util.*; import mindustry.*; import mindustry.ctype.*; import mindustry.entities.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.world.blocks.logic.LogicDisplay.*; import mindustry.world.blocks.logic.MemoryBlock.*; import mindustry.world.blocks.logic.MessageBlock.*; import static mindustry.Vars.*; public class LExecutor{ //special variables public static final int varCounter = 0, varTime = 1; public static final int maxGraphicsBuffer = 512, maxTextBuffer = 256; public LInstruction[] instructions = {}; public Var[] vars = {}; public LongSeq graphicsBuffer = new LongSeq(); public StringBuilder textBuffer = new StringBuilder(); public boolean initialized(){ return instructions != null && vars != null && instructions.length > 0; } /** Runs a single instruction. */ public void runOnce(){ //set time vars[varTime].numval = Time.millis(); //reset to start if(vars[varCounter].numval >= instructions.length) vars[varCounter].numval = 0; if(vars[varCounter].numval < instructions.length){ instructions[(int)(vars[varCounter].numval++)].run(this); } } public void load(String data){ load(LAssembler.assemble(data)); } /** Loads with a specified assembler. Resets all variables. */ public void load(LAssembler builder){ vars = new Var[builder.vars.size]; instructions = builder.instructions; builder.vars.each((name, var) -> { Var dest = new Var(name); vars[var.id] = dest; dest.constant = var.constant; if(var.value instanceof Number){ dest.isobj = false; dest.numval = ((Number)var.value).doubleValue(); }else{ dest.isobj = true; dest.objval = var.value; } }); } //region utility @Nullable Building building(int index){ Object o = vars[index].objval; return vars[index].isobj && o instanceof Building ? (Building)o : null; } @Nullable Object obj(int index){ Object o = vars[index].objval; return vars[index].isobj ? o : null; } boolean bool(int index){ Var v = vars[index]; return v.isobj ? v.objval != null : Math.abs(v.numval) >= 0.00001; } double num(int index){ Var v = vars[index]; return v.isobj ? 1 : v.numval; } int numi(int index){ return (int)num(index); } void setnum(int index, double value){ Var v = vars[index]; if(v.constant) return; v.numval = value; v.objval = null; v.isobj = false; } void setobj(int index, Object value){ Var v = vars[index]; if(v.constant) return; v.objval = value; v.isobj = true; } //endregion public static class Var{ public final String name; public boolean isobj, constant; public Object objval; public double numval; public Var(String name){ this.name = name; } } //region instruction types public interface LInstruction{ void run(LExecutor exec); } /** Controls a building's state. */ public static class ControlI implements LInstruction{ public int target; public LAccess type = LAccess.enabled; public int p1, p2, p3, p4; public ControlI(LAccess type, int target, int p1, int p2, int p3, int p4){ this.type = type; this.target = target; this.p1 = p1; this.p2 = p2; this.p3 = p3; this.p4 = p4; } ControlI(){} @Override public void run(LExecutor exec){ Object obj = exec.obj(target); if(obj instanceof Controllable){ Controllable cont = (Controllable)obj; cont.control(type, exec.num(p1), exec.num(p2), exec.num(p3), exec.num(p4)); } } } public static class ReadI implements LInstruction{ public int target, position, output; public ReadI(int target, int position, int output){ this.target = target; this.position = position; this.output = output; } public ReadI(){ } @Override public void run(LExecutor exec){ int address = exec.numi(position); Building from = exec.building(target); if(from instanceof MemoryEntity){ MemoryEntity mem = (MemoryEntity)from; exec.setnum(output, address < 0 || address >= mem.memory.length ? 0 : mem.memory[address]); } } } public static class WriteI implements LInstruction{ public int target, position, value; public WriteI(int target, int position, int value){ this.target = target; this.position = position; this.value = value; } public WriteI(){ } @Override public void run(LExecutor exec){ int address = exec.numi(position); Building from = exec.building(target); if(from instanceof MemoryEntity){ MemoryEntity mem = (MemoryEntity)from; if(address >= 0 && address < mem.memory.length){ mem.memory[address] = exec.num(value); } } } } public static class SenseI implements LInstruction{ public int from, to, type; public SenseI(int from, int to, int type){ this.from = from; this.to = to; this.type = type; } public SenseI(){ } @Override public void run(LExecutor exec){ Object target = exec.obj(from); Object sense = exec.obj(type); double output = 0; if(target instanceof Senseable){ if(sense instanceof Content){ output = ((Senseable)target).sense(((Content)sense)); }else if(sense instanceof LAccess){ output = ((Senseable)target).sense(((LAccess)sense)); } } exec.setnum(to, output); } } public static class RadarI implements LInstruction{ public RadarTarget target1 = RadarTarget.enemy, target2 = RadarTarget.any, target3 = RadarTarget.any; public RadarSort sort = RadarSort.distance; public int radar, sortOrder, output; //radar instructions are special in that they cache their output and only change it at fixed intervals. //this prevents lag from spam of radar instructions public Healthc lastTarget; public Interval timer = new Interval(); static float bestValue = 0f; static Unit best = null; public RadarI(RadarTarget target1, RadarTarget target2, RadarTarget target3, RadarSort sort, int radar, int sortOrder, int output){ this.target1 = target1; this.target2 = target2; this.target3 = target3; this.sort = sort; this.radar = radar; this.sortOrder = sortOrder; this.output = output; } public RadarI(){ } @Override public void run(LExecutor exec){ Building target = exec.building(radar); int sortDir = exec.bool(sortOrder) ? 1 : -1; if(target instanceof Ranged){ float range = ((Ranged)target).range(); Healthc targeted; if(timer.get(30f)){ //if any of the targets involve enemies boolean enemies = target1 == RadarTarget.enemy || target2 == RadarTarget.enemy || target3 == RadarTarget.enemy; best = null; bestValue = 0; if(enemies){ for(Team enemy : state.teams.enemiesOf(target.team)){ find(target, range, sortDir, enemy); } }else{ find(target, range, sortDir, target.team); } lastTarget = targeted = best; }else{ targeted = lastTarget; } exec.setobj(output, targeted); } } void find(Building b, float range, int sortDir, Team team){ Units.nearby(team, b.x, b.y, range, u -> { boolean valid = target1.func.get(b.team, u) && target2.func.get(b.team, u) && target3.func.get(b.team, u); if(!valid) return; float val = sort.func.get(b, u) * sortDir; if(val > bestValue || best == null){ bestValue = val; best = u; } }); } } public static class SetI implements LInstruction{ public int from, to; public SetI(int from, int to){ this.from = from; this.to = to; } SetI(){} @Override public void run(LExecutor exec){ Var v = exec.vars[to]; Var f = exec.vars[from]; //TODO error out when the from-value is a constant if(!v.constant){ if(f.isobj){ v.objval = f.objval; v.isobj = true; }else{ v.numval = f.numval; v.isobj = false; } } } } public static class BinaryOpI implements LInstruction{ public BinaryOp op = BinaryOp.add; public int a, b, dest; public BinaryOpI(BinaryOp op, int a, int b, int dest){ this.op = op; this.a = a; this.b = b; this.dest = dest; } BinaryOpI(){} @Override public void run(LExecutor exec){ exec.setnum(dest, op.function.get(exec.num(a), exec.num(b))); } } public static class UnaryOpI implements LInstruction{ public UnaryOp op = UnaryOp.negate; public int value, dest; public UnaryOpI(UnaryOp op, int value, int dest){ this.op = op; this.value = value; this.dest = dest; } UnaryOpI(){} @Override public void run(LExecutor exec){ exec.setnum(dest, op.function.get(exec.num(value))); } } public static class EndI implements LInstruction{ @Override public void run(LExecutor exec){ exec.vars[varCounter].numval = exec.instructions.length; } } public static class NoopI implements LInstruction{ @Override public void run(LExecutor exec){} } public static class DrawI implements LInstruction{ public byte type; public int target; public int x, y, p1, p2, p3; public DrawI(byte type, int target, int x, int y, int p1, int p2, int p3){ this.type = type; this.target = target; this.x = x; this.y = y; this.p1 = p1; this.p2 = p2; this.p3 = p3; } public DrawI(){ } @Override public void run(LExecutor exec){ //graphics on headless servers are useless. if(Vars.headless) return; //add graphics calls, cap graphics buffer size if(exec.graphicsBuffer.size < maxGraphicsBuffer){ exec.graphicsBuffer.add(DisplayCmd.get(type, exec.numi(x), exec.numi(y), exec.numi(p1), exec.numi(p2), exec.numi(p3))); } } } public static class DrawFlushI implements LInstruction{ public int target; public DrawFlushI(int target){ this.target = target; } public DrawFlushI(){ } @Override public void run(LExecutor exec){ //graphics on headless servers are useless. if(Vars.headless) return; Building build = exec.building(target); if(build instanceof LogicDisplayEntity){ LogicDisplayEntity d = (LogicDisplayEntity)build; for(int i = 0; i < exec.graphicsBuffer.size; i++){ d.commands.addLast(exec.graphicsBuffer.items[i]); } exec.graphicsBuffer.clear(); } } } public static class PrintI implements LInstruction{ public int value; public PrintI(int value){ this.value = value; } PrintI(){} @Override public void run(LExecutor exec){ if(exec.textBuffer.length() >= maxTextBuffer) return; //this should avoid any garbage allocation Var v = exec.vars[value]; if(v.isobj && value != 0){ String strValue = v.objval instanceof String ? (String)v.objval : v.objval == null ? "null" : "[object]"; exec.textBuffer.append(strValue); }else{ //display integer version when possible if(Math.abs(v.numval - (long)v.numval) < 0.000001){ exec.textBuffer.append((long)v.numval); }else{ exec.textBuffer.append(v.numval); } } } } public static class PrintFlushI implements LInstruction{ public int target; public PrintFlushI(int target){ this.target = target; } public PrintFlushI(){ } @Override public void run(LExecutor exec){ Building build = exec.building(target); if(build instanceof MessageBlockEntity){ MessageBlockEntity d = (MessageBlockEntity)build; d.message.setLength(0); d.message.append(exec.textBuffer, 0, Math.min(exec.textBuffer.length(), maxTextBuffer)); exec.textBuffer.setLength(0); } } } public static class JumpI implements LInstruction{ public ConditionOp op = ConditionOp.notEqual; public int value, compare, address; public JumpI(ConditionOp op, int value, int compare, int address){ this.op = op; this.value = value; this.compare = compare; this.address = address; } public JumpI(){ } @Override public void run(LExecutor exec){ if(address != -1 && op.function.get(exec.num(value), exec.num(compare))){ exec.vars[varCounter].numval = address; } } } public static class GetBuildI implements LInstruction{ public int dest; public int x, y; public GetBuildI(int dest, int x, int y){ this.dest = dest; this.x = x; this.y = y; } GetBuildI(){} @Override public void run(LExecutor exec){ exec.setobj(dest, Vars.world.build(exec.numi(x), exec.numi(y))); } } //endregion }