Merge branch 'master' into pr-readwrite

This commit is contained in:
WayZer
2024-02-14 15:13:31 +08:00
committed by GitHub
378 changed files with 11894 additions and 6855 deletions

View File

@@ -27,34 +27,63 @@ public class GlobalVars{
public static final Rand rand = new Rand();
//non-constants that depend on state
private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime;
private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
private ObjectIntMap<String> namesToIds = new ObjectIntMap<>();
private Seq<Var> vars = new Seq<>(Var.class);
private Seq<VarEntry> varEntries = new Seq<>();
private IntSet privilegedIds = new IntSet();
private UnlockableContent[][] logicIdToContent;
private int[][] contentIdToLogicId;
public void init(){
put("the end", null);
putEntryOnly("sectionProcessor");
putEntryOnly("@this");
putEntryOnly("@thisx");
putEntryOnly("@thisy");
putEntryOnly("@links");
putEntryOnly("@ipt");
putEntryOnly("sectionGeneral");
put("the end", null, false, true);
//add default constants
put("false", 0);
put("true", 1);
put("null", null);
putEntry("false", 0);
putEntry("true", 1);
put("null", null, false, true);
//math
put("@pi", Mathf.PI);
put("π", Mathf.PI); //for the "cool" kids
put("@e", Mathf.E);
put("@degToRad", Mathf.degRad);
put("@radToDeg", Mathf.radDeg);
putEntry("@pi", Mathf.PI);
put("π", Mathf.PI, false, true); //for the "cool" kids
putEntry("@e", Mathf.E);
putEntry("@degToRad", Mathf.degRad);
putEntry("@radToDeg", Mathf.radDeg);
putEntryOnly("sectionMap");
//time
varTime = put("@time", 0);
varTick = put("@tick", 0);
varSecond = put("@second", 0);
varMinute = put("@minute", 0);
varWave = put("@waveNumber", 0);
varWaveTime = put("@waveTime", 0);
varTime = putEntry("@time", 0);
varTick = putEntry("@tick", 0);
varSecond = putEntry("@second", 0);
varMinute = putEntry("@minute", 0);
varWave = putEntry("@waveNumber", 0);
varWaveTime = putEntry("@waveTime", 0);
varMapW = putEntry("@mapw", 0);
varMapH = putEntry("@maph", 0);
putEntryOnly("sectionNetwork");
varServer = putEntry("@server", 0, true);
varClient = putEntry("@client", 0, true);
//privileged desynced client variables
varClientLocale = putEntry("@clientLocale", null, true);
varClientUnit = putEntry("@clientUnit", null, true);
varClientName = putEntry("@clientName", null, true);
varClientTeam = putEntry("@clientTeam", 0, true);
varClientMobile = putEntry("@clientMobile", 0, true);
//special enums
put("@ctrlProcessor", ctrlProcessor);
@@ -104,6 +133,8 @@ public class GlobalVars{
logicIdToContent = new UnlockableContent[ContentType.all.length][];
contentIdToLogicId = new int[ContentType.all.length][];
putEntryOnly("sectionLookup");
Fi ids = Core.files.internal("logicids.dat");
if(ids.exists()){
//read logic ID mapping data (generated in ImagePacker)
@@ -114,7 +145,7 @@ public class GlobalVars{
contentIdToLogicId[ctype.ordinal()] = new int[Vars.content.getBy(ctype).size];
//store count constants
put("@" + ctype.name() + "Count", amount);
putEntry("@" + ctype.name() + "Count", amount);
for(int i = 0; i < amount; i++){
String name = in.readUTF();
@@ -147,6 +178,26 @@ public class GlobalVars{
//wave state
vars.items[varWave].numval = state.wave;
vars.items[varWaveTime].numval = state.wavetime / 60f;
vars.items[varMapW].numval = world.width();
vars.items[varMapH].numval = world.height();
//network
vars.items[varServer].numval = (net.server() || !net.active()) ? 1 : 0;
vars.items[varClient].numval = net.client() ? 1 : 0;
//client
if(!net.server() && player != null){
vars.items[varClientLocale].objval = player.locale();
vars.items[varClientUnit].objval = player.unit();
vars.items[varClientName].objval = player.name();
vars.items[varClientTeam].numval = player.team().id;
vars.items[varClientMobile].numval = mobile ? 1 : 0;
}
}
public Seq<VarEntry> getEntries(){
return varEntries;
}
/** @return a piece of content based on its logic ID. This is not equivalent to content ID. */
@@ -161,23 +212,35 @@ public class GlobalVars{
return arr != null && content.id >= 0 && content.id < arr.length ? arr[content.id] : -1;
}
/** @return a constant ID > 0 if there is a constant with this name, otherwise -1. */
/**
* @return a constant ID > 0 if there is a constant with this name, otherwise -1.
* Attempt to get privileged variable id from non-privileged logic executor returns null constant id.
*/
public int get(String name){
return namesToIds.get(name, -1);
}
/** @return a constant variable by ID. ID is not bound checked and must be positive. */
public Var get(int id){
/**
* @return a constant variable by ID. ID is not bound checked and must be positive.
* Attempt to get privileged variable from non-privileged logic executor returns null constant
*/
public Var get(int id, boolean privileged){
if(!privileged && privilegedIds.contains(id)) return vars.get(namesToIds.get("null"));
return vars.items[id];
}
/** Sets a global variable by an ID returned from put(). */
public void set(int id, double value){
get(id).numval = value;
get(id, true).numval = value;
}
/** Adds a constant value by name. */
public int put(String name, Object value){
public int put(String name, Object value, boolean privileged){
return put(name, value, privileged, true);
}
/** Adds a constant value by name. */
public int put(String name, Object value, boolean privileged, boolean hidden){
int existingIdx = namesToIds.get(name, -1);
if(existingIdx != -1){ //don't overwrite existing vars (see #6910)
Log.debug("Failed to add global logic variable '@', as it already exists.", name);
@@ -195,7 +258,46 @@ public class GlobalVars{
int index = vars.size;
namesToIds.put(name, index);
if(privileged) privilegedIds.add(index);
vars.add(var);
if(!hidden){
varEntries.add(new VarEntry(index, name, "", "", privileged));
}
return index;
}
public int put(String name, Object value){
return put(name, value, false);
}
public int putEntry(String name, Object value){
return put(name, value, false, false);
}
public int putEntry(String name, Object value, boolean privileged){
return put(name, value, privileged, false);
}
public void putEntryOnly(String name){
varEntries.add(new VarEntry(0, name, "", "", false));
}
/** An entry that describes a variable for documentation purposes. This is *only* used inside UI for global variables. */
public static class VarEntry{
public int id;
public String name, description, icon;
public boolean privileged;
public VarEntry(int id, String name, String description, String icon, boolean privileged){
this.id = id;
this.name = name;
this.description = description;
this.icon = icon;
this.privileged = privileged;
}
public VarEntry(){
}
}
}

View File

@@ -0,0 +1,61 @@
package mindustry.logic;
import arc.*;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
public class GlobalVarsDialog extends BaseDialog{
public GlobalVarsDialog(){
super("@logic.globals");
addCloseButton();
shown(this::setup);
onResize(this::setup);
}
void setup(){
float prefWidth = Math.min(Core.graphics.getWidth() * 0.9f / Scl.scl(1f) - 240f, 600f);
cont.clearChildren();
cont.pane(t -> {
t.margin(10f).marginRight(16f);
t.defaults().fillX().fillY();
for(var entry : Vars.logicVars.getEntries()){
if(entry.name.startsWith("section")){
Color color = Pal.accent;
t.add("@lglobal." + entry.name).fillX().center().labelAlign(Align.center).colspan(4).color(color).padTop(4f).padBottom(2f).row();
t.image(Tex.whiteui).height(4f).color(color).colspan(4).padBottom(8f).row();
}else{
Color varColor = Pal.gray;
float stub = 8f, mul = 0.5f, pad = 4;
String desc = entry.description;
if(desc == null || desc.isEmpty()){
desc = Core.bundle.get("lglobal." + entry.name, "");
}
String fdesc = desc;
t.add(new Image(Tex.whiteui, varColor.cpy().mul(mul))).width(stub);
t.stack(new Image(Tex.whiteui, varColor), new Label(" " + entry.name + " ", Styles.outlineLabel)).padRight(pad);
t.add(new Image(Tex.whiteui, Pal.gray.cpy().mul(mul))).width(stub);
t.table(Tex.pane, out -> out.add(fdesc).style(Styles.outlineLabel).width(prefWidth).padLeft(2).padRight(2).wrap()).padRight(pad);
t.row();
t.add().fillX().colspan(4).height(4).row();
}
}
}).grow();
}
}

View File

@@ -21,6 +21,7 @@ public enum LAccess{
maxHealth,
heat,
shield,
armor,
efficiency,
progress,
timescale,
@@ -29,6 +30,10 @@ public enum LAccess{
y,
shootX,
shootY,
cameraX,
cameraY,
cameraWidth,
cameraHeight,
size,
dead,
range,
@@ -62,7 +67,7 @@ public enum LAccess{
all = values(),
senseable = Seq.select(all, t -> t.params.length <= 1).toArray(LAccess.class),
controls = Seq.select(all, t -> t.params.length > 0).toArray(LAccess.class),
settable = {x, y, rotation, team, flag, health, totalPower, payloadType};
settable = {x, y, rotation, speed, armor, health, team, flag, totalPower, payloadType};
LAccess(String... params){
this.params = params;

View File

@@ -15,6 +15,7 @@ public class LAssembler{
private static final int invalidNum = Integer.MIN_VALUE;
private int lastVar;
private boolean privileged;
/** Maps names to variable IDs. */
public ObjectMap<String, BVar> vars = new ObjectMap<>();
/** All instructions to be executed. */
@@ -35,6 +36,7 @@ public class LAssembler{
Seq<LStatement> st = read(data, privileged);
asm.instructions = st.map(l -> l.build(asm)).retainAll(l -> l != null).toArray(LInstruction.class);
asm.privileged = privileged;
return asm;
}

View File

@@ -14,10 +14,13 @@ import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.game.MapObjectives.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.logic.LogicFx.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.logic.*;
@@ -48,6 +51,7 @@ public class LExecutor{
public Var[] vars = {};
public Var counter;
public int[] binds;
public boolean yield;
public int iptIndex = -1;
public LongSeq graphicsBuffer = new LongSeq();
@@ -59,10 +63,14 @@ public class LExecutor{
public boolean privileged = false;
//yes, this is a minor memory leak, but it's probably not significant enough to matter
protected IntFloatMap unitTimeouts = new IntFloatMap();
protected static IntFloatMap unitTimeouts = new IntFloatMap();
//lookup variable by name, lazy init.
protected ObjectIntMap<String> nameMap;
static{
Events.on(ResetEvent.class, e -> unitTimeouts.clear());
}
boolean timeoutDone(Unit unit, float delay){
return Time.time >= unitTimeouts.get(unit.id) + delay;
}
@@ -122,7 +130,7 @@ public class LExecutor{
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 ? logicVars.get(-index) : vars[index];
return index < 0 ? logicVars.get(-index, privileged) : vars[index];
}
public @Nullable Var optionalVar(String name){
@@ -178,11 +186,23 @@ public class LExecutor{
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : v.numval;
}
/** Get num value from variable, convert null to NaN to handle it differently in some instructions */
public double numOrNan(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : Double.NaN : 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;
}
/** Get float value from variable, convert null to NaN to handle it differently in some instructions */
public float numfOrNan(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : Float.NaN : invalid(v.numval) ? 0 : (float)v.numval;
}
public int numi(int index){
return (int)num(index);
}
@@ -446,7 +466,6 @@ public class LExecutor{
case unbind -> {
//TODO is this a good idea? will allocate
unit.resetController();
exec.setobj(varUnit, null);
}
case within -> {
exec.setnum(p4, unit.within(x1, y1, d1) ? 1 : 0);
@@ -893,8 +912,10 @@ public class LExecutor{
if(!v.constant){
if(f.isobj){
v.objval = f.objval;
v.isobj = true;
if(to != varCounter){
v.objval = f.objval;
v.isobj = true;
}
}else{
v.numval = invalid(f.numval) ? 0 : f.numval;
v.isobj = false;
@@ -976,12 +997,6 @@ public class LExecutor{
//graphics on headless servers are useless.
if(Vars.headless || exec.graphicsBuffer.size >= maxGraphicsBuffer) return;
int num1 = exec.numi(p1);
if(type == LogicDisplay.commandImage){
num1 = exec.obj(p1) instanceof UnlockableContent u ? u.iconId : 0;
}
//explicitly unpack colorPack, it's pre-processed here
if(type == LogicDisplay.commandColorPack){
double packed = exec.num(x);
@@ -993,7 +1008,63 @@ public class LExecutor{
a = ((value & 0x000000ff));
exec.graphicsBuffer.add(DisplayCmd.get(LogicDisplay.commandColor, pack(r), pack(g), pack(b), pack(a), 0, 0));
}else if(type == LogicDisplay.commandPrint){
CharSequence str = exec.textBuffer;
if(str.length() > 0){
var data = Fonts.logic.getData();
int advance = (int)data.spaceXadvance, lineHeight = (int)data.lineHeight;
int xOffset, yOffset;
int align = p1; //p1 is not a variable, it's a raw align value. what a massive hack
int maxWidth = 0, lines = 1, lineWidth = 0;
for(int i = 0; i < str.length(); i++){
char next = str.charAt(i);
if(next == '\n'){
maxWidth = Math.max(maxWidth, lineWidth);
lineWidth = 0;
lines ++;
}else{
lineWidth ++;
}
}
maxWidth = Math.max(maxWidth, lineWidth);
float
width = maxWidth * advance,
height = lines * lineHeight,
ha = ((Align.isLeft(align) ? -1f : 0f) + 1f + (Align.isRight(align) ? 1f : 0f))/2f,
va = ((Align.isBottom(align) ? -1f : 0f) + 1f + (Align.isTop(align) ? 1f : 0f))/2f;
xOffset = -(int)(width * ha);
yOffset = -(int)(height * va) + (lines - 1) * lineHeight;
int curX = exec.numi(x), curY = exec.numi(y);
for(int i = 0; i < str.length(); i++){
char next = str.charAt(i);
if(next == '\n'){
//move Y down when newline is encountered
curY -= lineHeight;
curX = exec.numi(x); //reset
continue;
}
if(Fonts.logic.getData().hasGlyph(next)){
exec.graphicsBuffer.add(DisplayCmd.get(LogicDisplay.commandPrint, packSign(curX + xOffset), packSign(curY + yOffset), next, 0, 0, 0));
}
curX += advance;
}
exec.textBuffer.setLength(0);
}
}else{
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
exec.graphicsBuffer.add(DisplayCmd.get(type, packSign(exec.numi(x)), packSign(exec.numi(y)), packSign(num1), packSign(exec.numi(p2)), packSign(exec.numi(p3)), packSign(exec.numi(p4))));
}
@@ -1079,6 +1150,55 @@ public class LExecutor{
}
}
public static class FormatI implements LInstruction{
public int value;
public FormatI(int value){
this.value = value;
}
FormatI(){}
@Override
public void run(LExecutor exec){
if(exec.textBuffer.length() >= maxTextBuffer) return;
int placeholderIndex = -1;
int placeholderNumber = 10;
for(int i = 0; i < exec.textBuffer.length(); i++){
if(exec.textBuffer.charAt(i) == '{' && exec.textBuffer.length() - i > 2){
char numChar = exec.textBuffer.charAt(i + 1);
if(numChar >= '0' && numChar <= '9' && exec.textBuffer.charAt(i + 2) == '}'){
if(numChar - '0' < placeholderNumber){
placeholderNumber = numChar - '0';
placeholderIndex = i;
}
}
}
}
if(placeholderIndex == -1) return;
//this should avoid any garbage allocation
Var v = exec.var(value);
if(v.isobj && value != 0){
String strValue = PrintI.toString(v.objval);
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, strValue);
}else{
//display integer version when possible
if(Math.abs(v.numval - (long)v.numval) < 0.00001){
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, (long)v.numval + "");
}else{
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, v.numval + "");
}
}
}
}
public static class PrintFlushI implements LInstruction{
public int target;
@@ -1144,7 +1264,6 @@ public class LExecutor{
public int value;
public float curTime;
public long frameId;
public WaitI(int value){
this.value = value;
@@ -1160,11 +1279,8 @@ public class LExecutor{
}else{
//skip back to self.
exec.var(varCounter).numval --;
}
if(state.updateId != frameId){
exec.yield = true;
curTime += Time.delta / 60f;
frameId = state.updateId;
}
}
}
@@ -1175,6 +1291,7 @@ public class LExecutor{
public void run(LExecutor exec){
//skip back to self.
exec.var(varCounter).numval --;
exec.yield = true;
}
}
@@ -1289,13 +1406,20 @@ public class LExecutor{
exec.setobj(result, i < 0 || i >= builds.size ? null : builds.get(i));
}
}
case unitCount -> exec.setnum(result, data.units.size);
case unitCount -> {
UnitType type = exec.obj(extra) instanceof UnitType u ? u : null;
if(type == null){
exec.setnum(result, data.units.size);
}else{
exec.setnum(result, data.unitsByType[type.id].size);
}
}
case coreCount -> exec.setnum(result, data.cores.size);
case playerCount -> exec.setnum(result, data.players.size);
case buildCount -> {
Block block = exec.obj(extra) instanceof Block b ? b : null;
if(block == null){
exec.setobj(result, null);
exec.setnum(result, data.buildings.size);
}else{
exec.setnum(result, data.getBuildings(block).size);
}
@@ -1485,6 +1609,23 @@ public class LExecutor{
}
case ambientLight -> state.rules.ambientLight.fromDouble(exec.num(value));
case solarMultiplier -> state.rules.solarMultiplier = Math.max(exec.numf(value), 0f);
case ban -> {
Object cont = exec.obj(value);
if(cont instanceof Block b){
// Rebuild PlacementFragment if anything has changed
if(state.rules.bannedBlocks.add(b) && !headless) ui.hudfrag.blockfrag.rebuild();
}else if(cont instanceof UnitType u){
state.rules.bannedUnits.add(u);
}
}
case unban -> {
Object cont = exec.obj(value);
if(cont instanceof Block b){
if(state.rules.bannedBlocks.remove(b) && !headless) ui.hudfrag.blockfrag.rebuild();
}else if(cont instanceof UnitType u){
state.rules.bannedUnits.remove(u);
}
}
case unitHealth, unitBuildSpeed, unitCost, unitDamage, blockHealth, blockDamage, buildSpeed, rtsMinSquad, rtsMinWeight -> {
Team team = exec.team(p1);
if(team != null){
@@ -1555,11 +1696,12 @@ public class LExecutor{
public static class FlushMessageI implements LInstruction{
public MessageType type = MessageType.announce;
public int duration;
public int duration, outSuccess;
public FlushMessageI(MessageType type, int duration){
public FlushMessageI(MessageType type, int duration, int outSuccess){
this.type = type;
this.duration = duration;
this.outSuccess = outSuccess;
}
public FlushMessageI(){
@@ -1567,16 +1709,20 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
if(headless && type != MessageType.mission) return;
//set default to success
exec.setnum(outSuccess, 1);
if(headless && type != MessageType.mission) {
exec.textBuffer.setLength(0);
return;
}
//skip back to self until possible
//TODO this is guaranteed desync on servers - I don't see a good solution
if(
type == MessageType.announce && ui.hasAnnouncement() ||
type == MessageType.notify && ui.hudfrag.hasToast() ||
type == MessageType.toast && ui.hasAnnouncement()
){
exec.var(varCounter).numval --;
//set outSuccess=false to let user retry.
exec.setnum(outSuccess, 0);
return;
}
@@ -1630,9 +1776,9 @@ public class LExecutor{
}
public static class ExplosionI implements LInstruction{
public int team, x, y, radius, damage, air, ground, pierce;
public int team, x, y, radius, damage, air, ground, pierce, effect;
public ExplosionI(int team, int x, int y, int radius, int damage, int air, int ground, int pierce){
public ExplosionI(int team, int x, int y, int radius, int damage, int air, int ground, int pierce, int effect){
this.team = team;
this.x = x;
this.y = y;
@@ -1641,6 +1787,7 @@ public class LExecutor{
this.air = air;
this.ground = ground;
this.pierce = pierce;
this.effect = effect;
}
public ExplosionI(){
@@ -1652,19 +1799,21 @@ public class LExecutor{
Team t = exec.team(team);
//note that there is a radius cap
Call.logicExplosion(t, World.unconv(exec.numf(x)), World.unconv(exec.numf(y)), World.unconv(Math.min(exec.numf(radius), 100)), exec.numf(damage), exec.bool(air), exec.bool(ground), exec.bool(pierce));
Call.logicExplosion(t, World.unconv(exec.numf(x)), World.unconv(exec.numf(y)), World.unconv(Math.min(exec.numf(radius), 100)), exec.numf(damage), exec.bool(air), exec.bool(ground), exec.bool(pierce), exec.bool(effect));
}
}
@Remote(called = Loc.server, unreliable = true)
public static void logicExplosion(Team team, float x, float y, float radius, float damage, boolean air, boolean ground, boolean pierce){
public static void logicExplosion(Team team, float x, float y, float radius, float damage, boolean air, boolean ground, boolean pierce, boolean effect){
if(damage < 0f) return;
Damage.damage(team, x, y, radius, damage, pierce, air, ground);
if(pierce){
Fx.spawnShockwave.at(x, y, World.conv(radius));
}else{
Fx.dynamicExplosion.at(x, y, World.conv(radius) / 8f);
if(effect){
if(pierce){
Fx.spawnShockwave.at(x, y, World.conv(radius));
}else{
Fx.dynamicExplosion.at(x, y, World.conv(radius) / 8f);
}
}
}
@@ -1856,5 +2005,148 @@ public class LExecutor{
}
}
public static class SetMarkerI implements LInstruction{
public LMarkerControl type = LMarkerControl.pos;
public int id, p1, p2, p3;
public SetMarkerI(LMarkerControl type, int id, int p1, int p2, int p3){
this.type = type;
this.id = id;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
public SetMarkerI(){
}
@Override
public void run(LExecutor exec){
if(type == LMarkerControl.remove){
state.markers.remove(exec.numi(id));
}else{
var marker = state.markers.get(exec.numi(id));
if(marker == null) return;
if(type == LMarkerControl.flushText){
marker.setText(exec.textBuffer.toString(), exec.bool(p1));
exec.textBuffer.setLength(0);
}else if(type == LMarkerControl.texture){
if(exec.bool(p1)){
marker.setTexture(exec.textBuffer.toString());
exec.textBuffer.setLength(0);
}else{
marker.setTexture(PrintI.toString(exec.obj(p2)));
}
}else{
marker.control(type, exec.numOrNan(p1), exec.numOrNan(p2), exec.numOrNan(p3));
}
}
}
}
public static class MakeMarkerI implements LInstruction{
//TODO arbitrary number
public static final int maxMarkers = 20000;
public String type = "shape";
public int id, x, y, replace;
public MakeMarkerI(String type, int id, int x, int y, int replace){
this.type = type;
this.id = id;
this.x = x;
this.y = y;
this.replace = replace;
}
public MakeMarkerI(){
}
@Override
public void run(LExecutor exec){
var cons = MapObjectives.markerNameToType.get(type);
if(cons != null && state.markers.size() < maxMarkers){
int mid = exec.numi(id);
if(exec.bool(replace) || !state.markers.has(mid)){
var marker = cons.get();
marker.control(LMarkerControl.pos, exec.num(x), exec.num(y), 0);
state.markers.add(mid, marker);
}
}
}
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void createMarker(int id, ObjectiveMarker marker){
state.markers.add(id, marker);
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void removeMarker(int id){
state.markers.remove(id);
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void updateMarker(int id, LMarkerControl control, double p1, double p2, double p3){
var marker = state.markers.get(id);
if(marker != null){
marker.control(control, p1, p2, p3);
}
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void updateMarkerText(int id, LMarkerControl type, boolean fetch, String text){
var marker = state.markers.get(id);
if(marker != null){
if(type == LMarkerControl.flushText){
marker.setText(text, fetch);
}
}
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void updateMarkerTexture(int id, String textureName){
var marker = state.markers.get(id);
if(marker != null){
marker.setTexture(textureName);
}
}
public static class LocalePrintI implements LInstruction{
public int name;
public LocalePrintI(int name){
this.name = name;
}
public LocalePrintI(){
}
@Override
public void run(LExecutor exec){
if(exec.textBuffer.length() >= maxTextBuffer) return;
//this should avoid any garbage allocation
Var v = exec.var(name);
if(v.isobj){
String name = PrintI.toString(v.objval);
String strValue;
if(mobile){
strValue = state.mapLocales.containsProperty(name + ".mobile") ?
state.mapLocales.getProperty(name + ".mobile") :
state.mapLocales.getProperty(name);
}else{
strValue = state.mapLocales.getProperty(name);
}
exec.textBuffer.append(strValue);
}
}
}
//endregion
}

View File

@@ -0,0 +1,33 @@
package mindustry.logic;
public enum LMarkerControl{
remove,
world("true/false"),
minimap("true/false"),
autoscale("true/false"),
pos("x", "y"),
endPos("x", "y"),
drawLayer("layer"),
color("color"),
radius("radius"),
stroke("stroke"),
rotation("rotation"),
shape("sides", "fill", "outline"),
flushText("fetch"),
fontSize("size"),
textHeight("height"),
labelFlags("background", "outline"),
texture("printFlush", "name"),
textureSize("width", "height"),
posi("index", "x", "y"),
uvi("index", "x", "y"),
colori("index", "color");
public final String[] params;
public static final LMarkerControl[] all = values();
LMarkerControl(String... params){
this.params = params;
}
}

View File

@@ -2,6 +2,7 @@ package mindustry.logic;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
@@ -10,10 +11,12 @@ import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.LCanvas.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import static mindustry.Vars.ui;
import static mindustry.logic.LCanvas.*;
/**
@@ -108,6 +111,35 @@ public abstract class LStatement{
return field(table, value, setter).width(85f).padRight(10).left();
}
/** Puts the text and field in one table, taking up one cell. */
protected Cell<TextField> fieldst(Table table, String desc, String value, Cons<String> setter){
Cell[] result = {null};
table.table(t -> {
t.setColor(table.color);
t.add(desc).padLeft(10).left().self(this::param);
result[0] = field(t, value, setter).width(85f).padRight(10).left();
});
return result[0];
}
/** Adds color edit button */
protected Cell<Button> col(Table table, String value, Cons<Color> setter){
return table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> {
Color current = Pal.accent.cpy();
if(value.startsWith("%")){
try{
current = Color.valueOf(value.substring(1));
}catch(Exception ignored){}
}
ui.picker.show(current, setter);
});
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
}
protected Cell<TextField> fields(Table table, String value, Cons<String> setter){
return field(table, value, setter).width(85f);
}
@@ -132,7 +164,7 @@ public abstract class LStatement{
if(p instanceof Enum e){
tooltip(c, e);
}
}).checked(current == p).group(group));
}).checked(current.equals(p)).group(group));
if(++i % cols == 0) t.row();
}

View File

@@ -6,10 +6,12 @@ import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.LCanvas.*;
@@ -120,6 +122,20 @@ public class LStatements{
@RegisterStatement("draw")
public static class DrawStatement extends LStatement{
static final String[] aligns = {"center", "top", "bottom", "left", "right", "topLeft", "topRight", "bottomLeft", "bottomRight"};
//yes, boxing Integer is gross but this is easier to construct and Integers <128 don't allocate anyway
static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
"center", Align.center,
"top", Align.top,
"bottom", Align.bottom,
"left", Align.left,
"right", Align.right,
"topLeft", Align.topLeft,
"topRight", Align.topRight,
"bottomLeft", Align.bottomLeft,
"bottomRight", Align.bottomRight
);
public GraphicsType type = GraphicsType.clear;
public String x = "0", y = "0", p1 = "0", p2 = "0", p3 = "0", p4 = "0";
@@ -146,6 +162,11 @@ public class LStatements{
p2 = "32";
p3 = "0";
}
if(type == GraphicsType.print){
p1 = "bottomLeft";
}
rebuild(table);
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
@@ -173,6 +194,10 @@ public class LStatements{
}
case col -> {
fields(s, "color", x, v -> x = v).width(144f);
col(s, x, res -> {
x = "%" + res.toString().substring(0, res.a >= 1f ? 6 : 8);
build(table);
});
}
case stroke -> {
s.add().width(4);
@@ -220,14 +245,21 @@ public class LStatements{
row(s);
fields(s, "rotation", p3, v -> p3 = v);
}
//TODO
/*
case character -> {
case print -> {
fields(s, "x", x, v -> x = v);
fields(s, "y", y, v -> y = v);
row(s);
fields(s, "char", p1, v -> p1 = v);
}*/
s.add("align ");
s.button(b -> {
b.label(() -> nameToAlign.containsKey(p1) ? p1 : "bottomLeft");
b.clicked(() -> showSelect(b, aligns, p1, t -> {
p1 = t;
}, 2, cell -> cell.size(165, 50)));
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
}
}
}).expand().left();
}
@@ -242,7 +274,8 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new DrawI((byte)type.ordinal(), 0, builder.var(x), builder.var(y), builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
return new DrawI((byte)type.ordinal(), 0, builder.var(x), builder.var(y),
type == GraphicsType.print ? nameToAlign.get(p1, Align.bottomLeft) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
}
@Override
@@ -272,6 +305,27 @@ public class LStatements{
}
}
@RegisterStatement("format")
public static class FormatStatement extends LStatement{
public String value = "\"frog\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public LInstruction build(LAssembler builder){
return new FormatI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.io;
}
}
@RegisterStatement("drawflush")
public static class DrawFlushStatement extends LStatement{
public String target = "display1";
@@ -1392,6 +1446,11 @@ public class LStatements{
row(table);
field(table, value, s -> value = s);
}
case ban, unban -> {
table.add(" block/unit ");
field(table, value, s -> value = s);
}
default -> {
table.add(" = ");
@@ -1419,7 +1478,7 @@ public class LStatements{
@RegisterStatement("message")
public static class FlushMessageStatement extends LStatement{
public MessageType type = MessageType.announce;
public String duration = "3";
public String duration = "3", outSuccess = "success";
@Override
public void build(Table table){
@@ -1438,12 +1497,14 @@ public class LStatements{
}, Styles.logict, () -> {}).size(160f, 40f).padLeft(2).color(table.color);
switch(type){
case announce, toast -> {
case announce, toast -> {
table.add(" for ");
fields(table, duration, str -> duration = str);
table.add(" secs ");
}
}
table.add(" success ");
fields(table, outSuccess, str -> outSuccess = str);
}
@Override
@@ -1453,7 +1514,7 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new FlushMessageI(type, builder.var(duration));
return new FlushMessageI(type, builder.var(duration), builder.var(outSuccess));
}
@Override
@@ -1546,22 +1607,10 @@ public class LStatements{
if(entry.color){
fields(table, "color", color, str -> color = str).width(120f);
table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> {
Color current = Pal.accent.cpy();
if(color.startsWith("%")){
try{
current = Color.valueOf(color.substring(1));
}catch(Exception ignored){}
}
ui.picker.show(current, result -> {
color = "%" + result.toString().substring(0, result.a >= 1f ? 6 : 8);
build(table);
});
});
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
col(table, color, res -> {
color = "%" + res.toString().substring(0, res.a >= 1f ? 6 : 8);
build(table);
});
}
row(table);
@@ -1594,7 +1643,7 @@ public class LStatements{
@RegisterStatement("explosion")
public static class ExplosionStatement extends LStatement{
public String team = "@crux", x = "0", y = "0", radius = "5", damage = "50", air = "true", ground = "true", pierce = "false";
public String team = "@crux", x = "0", y = "0", radius = "5", damage = "50", air = "true", ground = "true", pierce = "false", effect = "true";
@Override
public void build(Table table){
@@ -1609,6 +1658,8 @@ public class LStatements{
row(table);
fields(table, "ground", ground, str -> ground = str);
fields(table, "pierce", pierce, str -> pierce = str);
table.row();
fields(table, "effect", effect, str -> effect = str);
}
@Override
@@ -1618,7 +1669,7 @@ public class LStatements{
@Override
public LInstruction build(LAssembler b){
return new ExplosionI(b.var(team), b.var(x), b.var(y), b.var(radius), b.var(damage), b.var(air), b.var(ground), b.var(pierce));
return new ExplosionI(b.var(team), b.var(x), b.var(y), b.var(radius), b.var(damage), b.var(air), b.var(ground), b.var(pierce), b.var(effect));
}
@Override
@@ -1689,7 +1740,7 @@ public class LStatements{
fields(table, index, i -> index = i);
}
if(type == FetchType.buildCount || type == FetchType.build){
if(type == FetchType.buildCount || type == FetchType.build || type == FetchType.unitCount){
row(table);
fields(table, "block", extra, i -> extra = i);
@@ -1912,4 +1963,162 @@ public class LStatements{
return LCategory.world;
}
}
@RegisterStatement("setmarker")
public static class SetMarkerStatement extends LStatement{
public LMarkerControl type = LMarkerControl.pos;
public String id = "0", p1 = "0", p2 = "0", p3 = "0";
@Override
public void build(Table table){
rebuild(table);
}
void rebuild(Table table){
table.clearChildren();
table.add("set");
table.button(b -> {
b.label(() -> type.name());
b.clicked(() -> showSelect(b, LMarkerControl.all, type, t -> {
type = t;
rebuild(table);
}, 3, cell -> cell.size(140, 50)));
}, Styles.logict, () -> {}).size(190, 40).color(table.color).left().padLeft(2);
row(table);
fieldst(table, "of id#", id, str -> id = str);
//Q: why don't you just use arrays for this?
//A: arrays aren't as easy to serialize so the code generator doesn't handle them
for(int f = 0; f < type.params.length; f++){
int i = f;
table.table(t -> {
t.setColor(table.color);
String value = i == 0 ? p1 : i == 1 ? p2 : p3;
Cons<String> setter = i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : v -> p3 = v;
fields(t, type.params[i], value, setter).width(100f);
if(type == LMarkerControl.color || (type == LMarkerControl.colori && i == 1)){
col(t, value, res -> {
setter.get("%" + res.toString().substring(0, res.a >= 1f ? 6 : 8));
build(table);
});
}else if(type == LMarkerControl.drawLayer){
t.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> showSelectTable(b, (o, hide) -> {
o.row();
o.table(s -> {
s.left();
for(var field : Layer.class.getFields()){
float layer = Reflect.get(field);
s.button(field.getName() + " = " + layer, Styles.logicTogglet, () -> {
p1 = Float.toString(layer);
rebuild(table);
hide.run();
}).size(240f, 40f).row();
}
}).width(240f).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
}
});
if(i == 0) row(table);
if(i == 2) table.row();
}
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new SetMarkerI(type, builder.var(id), builder.var(p1), builder.var(p2), builder.var(p3));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
@RegisterStatement("makemarker")
public static class MakeMarkerStatement extends LStatement{
public String type = "shape", id = "0", x = "0", y = "0", replace = "true";
@Override
public void build(Table table){
table.clearChildren();
table.button(b -> {
b.label(() -> type);
b.clicked(() -> showSelect(b, MapObjectives.allMarkerTypeNames.toArray(String.class), type, t -> {
type = t;
build(table);
}, 2, cell -> cell.size(160, 50)));
}, Styles.logict, () -> {}).size(190, 40).color(table.color).left().padLeft(2);
fieldst(table, "id", id, str -> id = str);
row(table);
fieldst(table, "x", x, v -> x = v);
fieldst(table, "y", y, v -> y = v);
row(table);
fieldst(table, "replace", replace, v -> replace = v);
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new MakeMarkerI(type, builder.var(id), builder.var(x), builder.var(y), builder.var(replace));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
@RegisterStatement("localeprint")
public static class LocalePrintStatement extends LStatement{
public String value = "\"name\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new LocalePrintI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
}

View File

@@ -26,6 +26,7 @@ public class LogicDialog extends BaseDialog{
Cons<String> consumer = s -> {};
boolean privileged;
@Nullable LExecutor executor;
GlobalVarsDialog globalsDialog = new GlobalVarsDialog();
public LogicDialog(){
super("logic");
@@ -51,7 +52,7 @@ public class LogicDialog extends BaseDialog{
add(buttons).growX().name("canvas");
}
private Color typeColor(Var s, Color color){
public static Color typeColor(Var s, Color color){
return color.set(
!s.isobj ? Pal.place :
s.objval == null ? Color.darkGray :
@@ -65,7 +66,7 @@ public class LogicDialog extends BaseDialog{
);
}
private String typeName(Var s){
public static String typeName(Var s){
return
!s.isobj ? "number" :
s.objval == null ? "null" :
@@ -178,6 +179,8 @@ public class LogicDialog extends BaseDialog{
});
dialog.addCloseButton();
dialog.buttons.button("@logic.globals", Icon.list, () -> globalsDialog.show()).size(210f, 64f);
dialog.show();
}).name("variables").disabled(b -> executor == null || executor.vars.length == 0);

View File

@@ -57,6 +57,12 @@ public class LogicFx{
return map.get(name);
}
/** Adds an effect entry to the map. */
public static void add(String name, EffectEntry entry){
entry.name = name;
map.put(name, entry);
}
public static String[] all(){
return map.orderedKeys().toArray(String.class);
}

View File

@@ -15,6 +15,8 @@ public enum LogicRule{
lighting,
ambientLight,
solarMultiplier,
ban,
unban,
//team specific
buildSpeed,