diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 82ad500884..f9cbaa5622 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -501,6 +501,7 @@ editor.default = [lightgray] details = Details... edit = Edit variables = Vars +logic.globals = Built-in Variables editor.name = Name: editor.spawn = Spawn Unit editor.removeunit = Remove Unit @@ -2330,6 +2331,49 @@ lst.makemarker = Create a new logic marker in the world.\nAn ID to identify this lst.setmarker = Set a property for a marker.\nThe ID used must be the same as in the Make Marker instruction.\n[accent]null []values are ignored. lst.localeprint = Add map locale property value to the text buffer.\nTo set map locale bundles in map editor, check [accent]Map Info > Locale Bundles[].\nIf client is a mobile device, tries to print a property ending in ".mobile" first. +lglobal.false = 0 +lglobal.true = 1 +lglobal.null = null +lglobal.@pi = The mathematical constant pi (3.141...) +lglobal.@e = The mathematical constant e (2.718...) +lglobal.@degToRad = Multiply by this number to convert degrees to radians +lglobal.@radToDeg = Multiply by this number to convert radians to degrees + +lglobal.@time = Playtime of current save, in milliseconds +lglobal.@tick = Playtime of current save, in ticks (1 second = 60 ticks) +lglobal.@second = Playtime of current save, in seconds +lglobal.@minute = Playtime of current save, in minutes +lglobal.@waveNumber = Current wave number, if waves are enabled +lglobal.@waveTime = Countdown timer for waves, in seconds +lglobal.@mapw = Map width in tiles +lglobal.@maph = Map height in tiles + +lglobal.sectionMap = Map +lglobal.sectionGeneral = General +lglobal.sectionNetwork = Network/Clientside [World Processor Only] +lglobal.sectionProcessor = Processor +lglobal.sectionLookup = Lookup + +lglobal.@this = The logic block executing the code +lglobal.@thisx = X coordinate of block executing the code +lglobal.@thisy = Y coordinate of block executing the code +lglobal.@links = Total number of blocks linked to this processors +lglobal.@ipt = Execution speed of the processor in instructions per tick (60 ticks = 1 second) + +lglobal.@unitCount = Total number of types of unit content in the game; used with the lookup instruction +lglobal.@blockCount = Total number of types of block content in the game; used with the lookup instruction +lglobal.@itemCount = Total number of types of item content in the game; used with the lookup instruction +lglobal.@liquidCount = Total number of types of liquid content in the game; used with the lookup instruction + +lglobal.@server = True if the code is running on a server or in singleplayer, false otherwise +lglobal.@client = True if the code is running on a client connected to a server + +lglobal.@clientLocale = Locale of the client running the code. For example: en_US +lglobal.@clientUnit = Unit of client running the code +lglobal.@clientName = Player name of client running the code +lglobal.@clientTeam = Team ID of client running the code +lglobal.@clientMobile = True is the client running the code is on mobile, false otherwise + logic.nounitbuild = [red]Unit building logic is not allowed here. lenum.type = Type of building/unit.\ne.g. for any router, this will return [accent]@router[].\nNot a string. diff --git a/core/src/mindustry/logic/GlobalVars.java b/core/src/mindustry/logic/GlobalVars.java index bd2a162e6d..fa07475c7f 100644 --- a/core/src/mindustry/logic/GlobalVars.java +++ b/core/src/mindustry/logic/GlobalVars.java @@ -27,45 +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, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile; + private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile; private ObjectIntMap namesToIds = new ObjectIntMap<>(); private Seq vars = new Seq<>(Var.class); + private Seq 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); - varServer = put("@server", 0, true); - varClient = put("@client", 0, true); + 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 = put("@clientLocale", null, true); - varClientUnit = put("@clientUnit", null, true); - varClientName = put("@clientName", null, true); - varClientTeam = put("@clientTeam", 0, true); - varClientMobile = put("@clientMobile", 0, true); + 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); @@ -115,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) @@ -125,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(); @@ -159,6 +179,9 @@ public class GlobalVars{ 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; @@ -173,6 +196,10 @@ public class GlobalVars{ } } + public Seq getEntries(){ + return varEntries; + } + /** @return a piece of content based on its logic ID. This is not equivalent to content ID. */ public @Nullable Content lookupContent(ContentType type, int id){ var arr = logicIdToContent[type.ordinal()]; @@ -209,6 +236,11 @@ public class GlobalVars{ /** Adds a constant value by name. */ 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); @@ -228,10 +260,44 @@ public class GlobalVars{ 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(){ + } + } } diff --git a/core/src/mindustry/logic/GlobalVarsDialog.java b/core/src/mindustry/logic/GlobalVarsDialog.java new file mode 100644 index 0000000000..4d4e883f19 --- /dev/null +++ b/core/src/mindustry/logic/GlobalVarsDialog.java @@ -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) - 220f, 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(); + } +} diff --git a/core/src/mindustry/logic/LogicDialog.java b/core/src/mindustry/logic/LogicDialog.java index ac2fd249a8..e49928e572 100644 --- a/core/src/mindustry/logic/LogicDialog.java +++ b/core/src/mindustry/logic/LogicDialog.java @@ -26,6 +26,7 @@ public class LogicDialog extends BaseDialog{ Cons 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); diff --git a/core/src/mindustry/world/blocks/logic/LogicBlock.java b/core/src/mindustry/world/blocks/logic/LogicBlock.java index 18093949ab..765186cce1 100644 --- a/core/src/mindustry/world/blocks/logic/LogicBlock.java +++ b/core/src/mindustry/world/blocks/logic/LogicBlock.java @@ -354,8 +354,6 @@ public class LogicBlock extends Block{ } } - asm.putConst("@mapw", world.width()); - asm.putConst("@maph", world.height()); asm.putConst("@links", executor.links.length); asm.putConst("@ipt", instructionsPerTick); diff --git a/gradle.properties b/gradle.properties index ef07990ffb..1451cdd8a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ org.gradle.caching=true #used for slow jitpack builds; TODO see if this actually works org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 -archash=f3b941e548 +archash=a2acd2ee5f