Files
Mindustry/core/src/mindustry/logic/LExecutor.java
2021-02-09 14:52:23 -05:00

1019 lines
34 KiB
Java

package mindustry.logic;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.*;
import mindustry.ai.types.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.logic.LogicDisplay.*;
import mindustry.world.blocks.logic.MemoryBlock.*;
import mindustry.world.blocks.logic.MessageBlock.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LExecutor{
public static final int maxInstructions = 1000;
//for noise operations
public static final Simplex noise = new Simplex();
//special variables
public static final int
varCounter = 0,
varTime = 1,
varUnit = 2,
varThis = 3,
varTick = 4;
public static final int
maxGraphicsBuffer = 256,
maxDisplayBuffer = 1024,
maxTextBuffer = 256;
public LInstruction[] instructions = {};
public Var[] vars = {};
public int[] binds;
public LongSeq graphicsBuffer = new LongSeq();
public StringBuilder textBuffer = new StringBuilder();
public Building[] links = {};
public IntSet linkIds = new IntSet();
public Team team = Team.derelict;
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();
vars[varTick].numval = Time.time;
//reset to start
if(vars[varCounter].numval >= instructions.length || vars[varCounter].numval < 0){
vars[varCounter].numval = 0;
}
if(vars[varCounter].numval < instructions.length){
instructions[(int)(vars[varCounter].numval++)].run(this);
}
}
public void load(String data, int maxInstructions){
load(LAssembler.assemble(data, maxInstructions));
}
/** 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 number){
dest.isobj = false;
dest.numval = number.doubleValue();
}else{
dest.isobj = true;
dest.objval = var.value;
}
});
}
//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];
}
public @Nullable Building building(int index){
Object o = var(index).objval;
return var(index).isobj && o instanceof Building building ? building : null;
}
public @Nullable Object obj(int index){
Object o = var(index).objval;
return var(index).isobj ? o : null;
}
public boolean bool(int index){
Var v = var(index);
return v.isobj ? v.objval != null : Math.abs(v.numval) >= 0.00001;
}
public double num(int index){
Var v = var(index);
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 : invalid(v.numval) ? 0 : (float)v.numval;
}
public int numi(int index){
return (int)num(index);
}
public void setbool(int index, boolean value){
setnum(index, value ? 1 : 0);
}
public void setnum(int index, double value){
Var v = var(index);
if(v.constant) return;
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){
Var v = var(index);
if(v.constant) return;
v.objval = value;
v.isobj = true;
}
public void setconst(int index, Object value){
Var v = var(index);
v.objval = value;
v.isobj = true;
}
//endregion
/** A logic variable. */
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);
}
/** Binds the processor to a unit based on some filters. */
public static class UnitBindI implements LInstruction{
public int type;
public UnitBindI(int type){
this.type = type;
}
public UnitBindI(){
}
@Override
public void run(LExecutor exec){
if(exec.binds == null || exec.binds.length != content.units().size){
exec.binds = new int[content.units().size];
}
//binding to `null` was previously possible, but was too powerful and exploitable
if(exec.obj(type) instanceof UnitType type){
Seq<Unit> seq = exec.team.data().unitCache(type);
if(seq != null && seq.any()){
exec.binds[type.id] %= seq.size;
if(exec.binds[type.id] < seq.size){
//bind to the next unit
exec.setconst(varUnit, seq.get(exec.binds[type.id]));
}
exec.binds[type.id] ++;
}else{
//no units of this type found
exec.setconst(varUnit, null);
}
}else if(exec.obj(type) instanceof Unit u && u.team == exec.team){
//bind to specific unit object
exec.setconst(varUnit, u);
}else{
exec.setconst(varUnit, null);
}
}
}
/** Uses a unit to find something that may not be in its range. */
public static class UnitLocateI implements LInstruction{
public LLocate locate = LLocate.building;
public BlockFlag flag = BlockFlag.core;
public int enemy, ore;
public int outX, outY, outFound, outBuild;
public UnitLocateI(LLocate locate, BlockFlag flag, int enemy, int ore, int outX, int outY, int outFound, int outBuild){
this.locate = locate;
this.flag = flag;
this.enemy = enemy;
this.ore = ore;
this.outX = outX;
this.outY = outY;
this.outFound = outFound;
this.outBuild = outBuild;
}
public UnitLocateI(){
}
@Override
public void run(LExecutor exec){
Object unitObj = exec.obj(varUnit);
LogicAI ai = UnitControlI.checkLogicAI(exec, unitObj);
if(unitObj instanceof Unit unit && ai != null){
ai.controlTimer = LogicAI.logicControlTimeout;
Cache cache = (Cache)ai.execCache.get(this, Cache::new);
if(ai.checkTargetTimer(this)){
Tile res = null;
boolean build = false;
switch(locate){
case ore -> {
if(exec.obj(ore) instanceof Item item){
res = indexer.findClosestOre(unit, item);
}
}
case building -> {
res = Geometry.findClosest(unit.x, unit.y, exec.bool(enemy) ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
build = true;
}
case spawn -> {
res = Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
case damaged -> {
Building b = Units.findDamagedTile(unit.team, unit.x, unit.y);
res = b == null ? null : b.tile;
build = true;
}
}
if(res != null && (!build || res.build != null)){
cache.found = true;
//set result if found
exec.setnum(outX, cache.x = World.conv(build ? res.build.x : res.worldx()));
exec.setnum(outY, cache.y = World.conv(build ? res.build.y : res.worldy()));
exec.setnum(outFound, 1);
}else{
cache.found = false;
exec.setnum(outFound, 0);
}
exec.setobj(outBuild, res != null && res.build != null && res.build.team == exec.team ? cache.build = res.build : null);
}else{
exec.setobj(outBuild, cache.build);
exec.setbool(outFound, cache.found);
exec.setnum(outX, cache.x);
exec.setnum(outY, cache.y);
}
}else{
exec.setbool(outFound, false);
}
}
static class Cache{
float x, y;
boolean found;
Building build;
}
}
/** Controls the unit based on some parameters. */
public static class UnitControlI implements LInstruction{
public LUnitControl type = LUnitControl.move;
public int p1, p2, p3, p4, p5;
public UnitControlI(LUnitControl type, int p1, int p2, int p3, int p4, int p5){
this.type = type;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.p4 = p4;
this.p5 = p5;
}
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());
((LogicAI)unit.controller()).controller = exec.building(varThis);
//clear old state
unit.mineTile = null;
unit.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;
float x1 = World.unconv(exec.numf(p1)), y1 = World.unconv(exec.numf(p2)), d1 = World.unconv(exec.numf(p3));
switch(type){
case move, stop, approach -> {
ai.control = type;
ai.moveX = x1;
ai.moveY = y1;
if(type == LUnitControl.approach){
ai.moveRad = d1;
}
//stop mining/building
if(type == LUnitControl.stop){
unit.mineTile = null;
unit.clearBuilding();
}
}
case within -> {
exec.setnum(p4, unit.within(x1, y1, d1) ? 1 : 0);
}
case pathfind -> {
ai.control = type;
}
case target -> {
ai.posTarget.set(x1, y1);
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 boost -> {
ai.boost = exec.bool(p1);
}
case flag -> {
unit.flag = exec.num(p1);
}
case mine -> {
Tile tile = world.tileWorld(x1, y1);
if(unit.canMine()){
unit.mineTile = unit.validMine(tile) ? tile : null;
}
}
case payDrop -> {
if(ai.payTimer > 0) return;
if(unit instanceof Payloadc pay && pay.hasPayload()){
Call.payloadDropped(unit, unit.x, unit.y);
ai.payTimer = LogicAI.transferDelay;
}
}
case payTake -> {
if(ai.payTimer > 0) return;
if(unit instanceof Payloadc pay){
//units
if(exec.bool(p1)){
Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type.hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(result != null){
Call.pickedUnitPayload(unit, result);
}
}else{ //buildings
Building tile = world.buildWorld(unit.x, unit.y);
//TODO copy pasted code
if(tile != null && tile.team == unit.team){
if(tile.block.buildVisibility != BuildVisibility.hidden && tile.canPickup() && pay.canPickup(tile)){
Call.pickedBuildPayload(unit, tile, true);
}else{ //pick up block payload
Payload current = tile.getPayload();
if(current != null && pay.canPickupPayload(current)){
Call.pickedBuildPayload(unit, tile, false);
}
}
}
}
ai.payTimer = LogicAI.transferDelay;
}
}
case build -> {
if(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
if(ai.plan.x != x || ai.plan.y != y || ai.plan.block != block || unit.plans.isEmpty()){
ai.plan.progress = 0;
ai.plan.initialized = false;
ai.plan.stuck = false;
}
ai.plan.set(x, y, rot, block);
ai.plan.config = exec.obj(p5) instanceof Content c ? c : null;
unit.clearBuilding();
if(ai.plan.tile() != null){
unit.updateBuilding = true;
unit.addBuild(ai.plan);
}
}
}
case getBlock -> {
float range = Math.max(unit.range(), buildingRange);
if(!unit.within(x1, y1, range)){
exec.setobj(p3, null);
exec.setobj(p4, null);
}else{
Tile tile = world.tileWorld(x1, y1);
//any environmental solid block is returned as StoneWall, aka "@solid"
Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block();
exec.setobj(p3, block);
exec.setobj(p4, tile != null && tile.build != null ? tile.build : null);
}
}
case itemDrop -> {
if(ai.itemTimer > 0) return;
Building build = exec.building(p1);
int dropped = Math.min(unit.stack.amount, exec.numi(p2));
if(build != null && build.isValid() && dropped > 0 && unit.within(build, logicItemTransferRange + build.block.size * tilesize/2f)){
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 && build.isValid() && build.items != null && exec.obj(p2) instanceof Item item && unit.within(build, logicItemTransferRange + build.block.size * tilesize/2f)){
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;
public LAccess type = LAccess.enabled;
public int p1, p2, p3, p4;
public Interval timer = new Interval(1);
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 Building b && b.team == exec.team && exec.linkIds.contains(b.id) && (type.cooldown <= 0 || timer.get(type.cooldown))){
if(type.isObj){
b.control(type, exec.obj(p1), exec.num(p2), exec.num(p3), exec.num(p4));
}else{
b.control(type, exec.num(p1), exec.num(p2), exec.num(p3), exec.num(p4));
}
}
}
}
public static class GetLinkI implements LInstruction{
public int output, index;
public GetLinkI(int output, int index){
this.index = index;
this.output = output;
}
public GetLinkI(){
}
@Override
public void run(LExecutor exec){
int address = exec.numi(index);
exec.setobj(output, address >= 0 && address < exec.links.length ? exec.links[address] : null);
}
}
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 MemoryBuild mem && from.team == exec.team){
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 MemoryBuild mem && from.team == exec.team){
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);
//note that remote units/buildings can be sensed as well
if(target instanceof Senseable se){
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(la));
}else{
//object output
exec.setobj(to, objOut);
}
}
}else{
exec.setobj(to, null);
}
}
}
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){
Object base = exec.obj(radar);
int sortDir = exec.bool(sortOrder) ? 1 : -1;
LogicAI ai = null;
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;
//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;
best = null;
bestValue = 0;
if(enemies){
Seq<TeamData> data = state.teams.present;
for(int i = 0; i < data.size; i++){
if(data.items[i].team != r.team()){
find(r, range, sortDir, data.items[i].team);
}
}
}else{
find(r, range, sortDir, r.team());
}
lastTarget = targeted = best;
}else{
targeted = lastTarget;
}
exec.setobj(output, targeted);
}else{
exec.setobj(output, null);
}
}
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);
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.var(to);
Var f = exec.var(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 = invalid(f.numval) ? 0 : f.numval;
v.isobj = false;
}
}
}
}
public static class OpI implements LInstruction{
public LogicOp op = LogicOp.add;
public int a, b, dest;
public OpI(LogicOp op, int a, int b, int dest){
this.op = op;
this.a = a;
this.b = b;
this.dest = dest;
}
OpI(){}
@Override
public void run(LExecutor exec){
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 both are objects
exec.setnum(dest, op.objFunction2.get(exec.obj(a), exec.obj(b)));
}else{
//otherwise use the numeric function
exec.setnum(dest, op.function2.get(exec.num(a), exec.num(b)));
}
}
}
}
public static class EndI implements LInstruction{
@Override
public void run(LExecutor exec){
exec.var(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, p4;
public DrawI(byte type, int target, int x, int y, int p1, int p2, int p3, int p4){
this.type = type;
this.target = target;
this.x = x;
this.y = y;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.p4 = p4;
}
public DrawI(){
}
@Override
public void run(LExecutor exec){
//graphics on headless servers are useless.
if(Vars.headless) return;
int num1 = exec.numi(p1);
if(type == LogicDisplay.commandImage){
num1 = exec.obj(p1) instanceof UnlockableContent u ? u.iconId : 0;
}
//add graphics calls, cap graphics buffer size
if(exec.graphicsBuffer.size < maxGraphicsBuffer){
exec.graphicsBuffer.add(DisplayCmd.get(type, exec.numi(x), exec.numi(y), num1, exec.numi(p2), exec.numi(p3), exec.numi(p4)));
}
}
}
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;
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]);
}
}
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.var(value);
if(v.isobj && value != 0){
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 :
v.objval instanceof Unit unit ? unit.type.name :
"[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){
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));
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){
Var va = exec.var(value);
Var vb = exec.var(compare);
boolean cmp;
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));
}
if(cmp){
exec.var(varCounter).numval = address;
}
}
}
}
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
}