Merge branch 'master' of https://github.com/Anuken/Mindustry into tileable-logic-displays

# Conflicts:
#	core/assets/icons/icons.properties
#	core/assets/logicids.dat
This commit is contained in:
Anuken
2025-04-14 22:50:47 -04:00
494 changed files with 17743 additions and 9826 deletions

View File

@@ -22,7 +22,8 @@ import static mindustry.Vars.*;
/** Stores global logic variables for logic processors. */
public class GlobalVars{
public static final int ctrlProcessor = 1, ctrlPlayer = 2, ctrlCommand = 3;
public static final ContentType[] lookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid};
public static final ContentType[] lookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid, ContentType.team};
public static final ContentType[] writableLookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid};
/** Global random state. */
public static final Rand rand = new Rand();
@@ -34,9 +35,9 @@ public class GlobalVars{
private ObjectSet<String> privilegedNames = new ObjectSet<>();
private UnlockableContent[][] logicIdToContent;
private int[][] contentIdToLogicId;
public static final Seq<String> soundNames = new Seq<>();
public void init(){
putEntryOnly("sectionProcessor");
@@ -73,7 +74,7 @@ public class GlobalVars{
varMapW = putEntry("@mapw", 0);
varMapH = putEntry("@maph", 0);
varWait = putEntry("@wait", null);
varWait = put("@wait", null, true, true);
putEntryOnly("sectionNetwork");
@@ -91,7 +92,7 @@ public class GlobalVars{
put("@ctrlProcessor", ctrlProcessor);
put("@ctrlPlayer", ctrlPlayer);
put("@ctrlCommand", ctrlCommand);
//sounds
if(Core.assets != null){
for(Sound sound : Core.assets.getAll(Sound.class, new Seq<>(Sound.class))){
@@ -155,7 +156,7 @@ public class GlobalVars{
if(ids.exists()){
//read logic ID mapping data (generated in ImagePacker)
try(DataInputStream in = new DataInputStream(ids.readByteStream())){
for(ContentType ctype : lookableContent){
for(ContentType ctype : writableLookableContent){
short amount = in.readShort();
logicIdToContent[ctype.ordinal()] = new UnlockableContent[amount];
contentIdToLogicId[ctype.ordinal()] = new int[Vars.content.getBy(ctype).size];
@@ -203,7 +204,7 @@ public class GlobalVars{
varClient.numval = net.client() ? 1 : 0;
//client
if(!net.server() && player != null){
if(player != null){
varClientLocale.objval = player.locale();
varClientUnit.objval = player.unit();
varClientName.objval = player.name();
@@ -220,8 +221,13 @@ public class GlobalVars{
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){
/** @return a piece of content based on its logic ID. This is not equivalent to content ID. In the case of teams, the return value may not be Content. */
public @Nullable Object lookupContent(ContentType type, int id){
//teams are a special case; they are not technically content, but can be looked up
if(type == ContentType.team){
return id >= 0 && id < 256 ? Team.all[id] : null;
}
var arr = logicIdToContent[type.ordinal()];
return arr != null && id >= 0 && id < arr.length ? arr[id] : null;
}

View File

@@ -18,6 +18,7 @@ public enum LAccess{
ammo,
ammoCapacity,
currentAmmoType,
memoryCapacity,
health,
maxHealth,
heat,
@@ -55,6 +56,8 @@ public enum LAccess{
name,
payloadCount,
payloadType,
totalPayload,
payloadCapacity,
id,
//values with parameters are considered controllable

View File

@@ -11,7 +11,8 @@ import mindustry.logic.LExecutor.*;
public class LAssembler{
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
private static final int invalidNum = Integer.MIN_VALUE;
private static final int invalidNumNegative = Integer.MIN_VALUE;
private static final int invalidNumPositive = Integer.MAX_VALUE;
private boolean privileged;
/** Maps names to variable. */
@@ -34,7 +35,7 @@ public class LAssembler{
Seq<LStatement> st = read(data, privileged);
asm.privileged = privileged;
asm.instructions = st.map(l -> l.build(asm)).retainAll(l -> l != null).toArray(LInstruction.class);
return asm;
}
@@ -72,9 +73,11 @@ public class LAssembler{
//remove spaces for non-strings
symbol = symbol.replace(' ', '_');
double value = parseDouble(symbol);
//use a positive invalid number if number might be negative, else use a negative invalid number
int usedInvalidNum = symbol.length() > 0 && symbol.charAt(0) == '-' ? invalidNumPositive : invalidNumNegative;
double value = parseDouble(symbol, usedInvalidNum);
if(value == invalidNum){
if(value == usedInvalidNum){
return putVar(symbol);
}else{
//this creates a hidden const variable with the specified value
@@ -82,10 +85,14 @@ public class LAssembler{
}
}
double parseDouble(String symbol){
double parseDouble(String symbol, int invalidNum){
//parse hex/binary syntax
if(symbol.startsWith("0b")) return Strings.parseLong(symbol, 2, 2, symbol.length(), invalidNum);
if(symbol.startsWith("+0b")) return Strings.parseLong(symbol, 2, 3, symbol.length(), invalidNum);
if(symbol.startsWith("-0b")) return -Strings.parseLong(symbol, 2, 3, symbol.length(), invalidNum);//FIXME: breaks with Long.MIN_VALUE
if(symbol.startsWith("0x")) return Strings.parseLong(symbol, 16, 2, symbol.length(), invalidNum);
if(symbol.startsWith("+0x")) return Strings.parseLong(symbol, 16, 3, symbol.length(), invalidNum);
if(symbol.startsWith("-0x")) return -Strings.parseLong(symbol, 16, 3, symbol.length(), invalidNum);//FIXME: breaks with Long.MIN_VALUE
if(symbol.startsWith("%") && (symbol.length() == 7 || symbol.length() == 9)) return parseColor(symbol);
return Strings.parseDouble(symbol, invalidNum);

View File

@@ -20,18 +20,21 @@ import mindustry.logic.LStatements.*;
import mindustry.ui.*;
public class LCanvas extends Table{
public static final int maxJumpsDrawn = 100;
private static final Seq<JumpCurve> tmpOccupiers1 = new Seq<>();
private static final Seq<JumpCurve> tmpOccupiers2 = new Seq<>();
private static final Bits tmpBits1 = new Bits();
private static final Bits tmpBits2 = new Bits();
private static final int invalidJump = Integer.MAX_VALUE; // terrible hack
//ew static variables
static LCanvas canvas;
private static final boolean dynamicJumpHeights = true;
public DragLayout statements;
public ScrollPane pane;
public Group jumps;
StatementElem dragging;
StatementElem hovered;
float targetWidth;
int jumpCount = 0;
boolean privileged;
Seq<Tooltip> tooltips = new Seq<>();
@@ -106,14 +109,15 @@ public class LCanvas extends Table{
clear();
statements = new DragLayout();
jumps = new WidgetGroup();
pane = pane(t -> {
t.center();
t.add(statements).pad(2f).center().width(targetWidth);
t.addChild(jumps);
t.addChild(statements.jumps);
jumps.cullable = false;
statements.jumps.touchable = Touchable.disabled;
statements.jumps.update(() -> statements.jumps.setCullingArea(t.getCullingArea()));
statements.jumps.cullable = false;
}).grow().get();
pane.setFlickScroll(false);
pane.setScrollYForce(s);
@@ -123,12 +127,6 @@ public class LCanvas extends Table{
}
}
@Override
public void draw(){
jumpCount = 0;
super.draw();
}
public void add(LStatement statement){
statements.addChild(new StatementElem(statement));
}
@@ -141,7 +139,7 @@ public class LCanvas extends Table{
}
public void load(String asm){
jumps.clear();
statements.jumps.clear();
Seq<LStatement> statements = LAssembler.read(asm, privileged);
statements.truncate(LExecutor.maxInstructions);
@@ -154,13 +152,12 @@ public class LCanvas extends Table{
st.setupUI();
}
this.statements.layout();
this.statements.updateJumpHeights = true;
}
public void clearStatements(){
jumps.clear();
statements.jumps.clear();
statements.clearChildren();
statements.layout();
}
StatementElem checkHovered(){
@@ -195,6 +192,11 @@ public class LCanvas extends Table{
Seq<Element> seq = new Seq<>();
int insertPosition = 0;
boolean invalidated;
public Group jumps = new WidgetGroup();
private Seq<JumpCurve> processedJumps = new Seq<>();
private IntMap<JumpCurve> reprBefore = new IntMap<>();
private IntMap<JumpCurve> reprAfter = new IntMap<>();
public boolean updateJumpHeights = true;
{
setTransform(true);
@@ -206,10 +208,12 @@ public class LCanvas extends Table{
float cy = 0;
seq.clear();
float totalHeight = getChildren().sumf(e -> e.getHeight() + space);
height = prefHeight = totalHeight;
width = prefWidth = Scl.scl(targetWidth);
float totalHeight = getChildren().sumf(e -> e.getPrefHeight() + space);
if(height != totalHeight || width != Scl.scl(targetWidth)){
height = prefHeight = totalHeight;
width = prefWidth = Scl.scl(targetWidth);
invalidateHierarchy();
}
//layout everything normally
for(int i = 0; i < getChildren().size; i++){
@@ -219,7 +223,7 @@ public class LCanvas extends Table{
if(dragging == e) continue;
e.setSize(width, e.getPrefHeight());
e.setPosition(0, height - cy, Align.topLeft);
e.setPosition(0, totalHeight - cy, Align.topLeft);
((StatementElem)e).updateAddress(i);
cy += e.getPrefHeight() + space;
@@ -250,13 +254,97 @@ public class LCanvas extends Table{
}
}
if(parent != null) parent.invalidateHierarchy();
if(dynamicJumpHeights){
if(updateJumpHeights) setJumpHeights();
updateJumpHeights = false;
}
if(parent != null && parent instanceof Table){
setCullingArea(parent.getCullingArea());
}
}
private void setJumpHeights(){
SnapshotSeq<Element> jumpsChildren = jumps.getChildren();
processedJumps.clear();
reprBefore.clear();
reprAfter.clear();
jumpsChildren.each(e -> {
if(!(e instanceof JumpCurve e2)) return;
e2.prepareHeight();
if(e2.jumpUIBegin == invalidJump) return;
if(e2.flipped){
JumpCurve prev = reprAfter.get(e2.jumpUIBegin);
if(prev != null && prev.jumpUIEnd >= e2.jumpUIEnd) return;
reprAfter.put(e2.jumpUIBegin, e2);
}else{
JumpCurve prev = reprBefore.get(e2.jumpUIEnd);
if(prev != null && prev.jumpUIBegin <= e2.jumpUIBegin) return;
reprBefore.put(e2.jumpUIEnd, e2);
}
});
processedJumps.add(reprBefore.values().toArray());
processedJumps.add(reprAfter.values().toArray());
processedJumps.sort((a, b) -> a.jumpUIBegin - b.jumpUIBegin);
Seq<JumpCurve> occupiers = tmpOccupiers1;
Bits occupied = tmpBits1;
occupiers.clear();
occupied.clear();
for(int i = 0; i < processedJumps.size; i++){
JumpCurve cur = processedJumps.get(i);
occupiers.retainAll(e -> {
if(e.jumpUIEnd > cur.jumpUIBegin) return true;
occupied.clear(e.predHeight);
return false;
});
int h = getJumpHeight(i, occupiers, occupied);
occupiers.add(cur);
occupied.set(h);
}
occupiers.clear();
jumpsChildren.each(e -> {
if(!(e instanceof JumpCurve e2)) return;
if(e2.jumpUIBegin == invalidJump) return;
e2.predHeight = e2.flipped ? reprAfter.get(e2.jumpUIBegin).predHeight : reprBefore.get(e2.jumpUIEnd).predHeight;
e2.markedDone = true;
});
}
private int getJumpHeight(int index, Seq<JumpCurve> occupiers, Bits occupied){
JumpCurve jmp = processedJumps.get(index);
if(jmp.markedDone) return jmp.predHeight;
Seq<JumpCurve> tmpOccupiers = tmpOccupiers2;
Bits tmpOccupied = tmpBits2;
tmpOccupiers.set(occupiers);
tmpOccupied.set(occupied);
int max = -1;
for(int i = index + 1; i < processedJumps.size; i++){
JumpCurve cur = processedJumps.get(i);
if(cur.jumpUIEnd > jmp.jumpUIEnd) continue;
tmpOccupiers.retainAll(e -> {
if(e.jumpUIEnd > cur.jumpUIBegin) return true;
tmpOccupied.clear(e.predHeight);
return false;
});
int h = getJumpHeight(i, tmpOccupiers, tmpOccupied);
tmpOccupiers.add(cur);
tmpOccupied.set(h);
max = Math.max(max, h);
}
jmp.predHeight = occupied.nextClearBit(max + 1);
jmp.markedDone = true;
tmpOccupiers.clear();
return jmp.predHeight;
}
@Override
public float getPrefWidth(){
return prefWidth;
@@ -312,9 +400,10 @@ public class LCanvas extends Table{
}
dragging = null;
invalidateHierarchy();
}
layout();
updateJumpHeights = true;
}
}
@@ -350,7 +439,7 @@ public class LCanvas extends Table{
t.button(Icon.cancel, Styles.logici, () -> {
remove();
dragging = null;
statements.layout();
statements.updateJumpHeights = true;
}).size(24f);
t.addListener(new InputListener(){
@@ -369,7 +458,8 @@ public class LCanvas extends Table{
lasty = v.y;
dragging = StatementElem.this;
toFront();
statements.layout();
statements.updateJumpHeights = true;
statements.invalidate();
return true;
}
@@ -381,7 +471,7 @@ public class LCanvas extends Table{
lastx = v.x;
lasty = v.y;
statements.layout();
statements.invalidate();
}
@Override
@@ -423,9 +513,9 @@ public class LCanvas extends Table{
StatementElem s = new StatementElem(copy);
statements.addChildAfter(StatementElem.this, s);
statements.layout();
copy.elem = s;
copy.setupUI();
statements.updateJumpHeights = true;
}
}
@@ -444,19 +534,20 @@ public class LCanvas extends Table{
public static class JumpButton extends ImageButton{
Color hoverColor = Pal.place;
Color defaultColor = Color.white;
Prov<StatementElem> to;
boolean selecting;
float mx, my;
ClickListener listener;
public JumpCurve curve;
public StatementElem elem;
public JumpButton(Prov<StatementElem> getter, Cons<StatementElem> setter){
public JumpButton(Prov<StatementElem> getter, Cons<StatementElem> setter, StatementElem elem){
super(Tex.logicNode, new ImageButtonStyle(){{
imageUpColor = Color.white;
}});
this.elem = elem;
to = getter;
addListener(listener = new ClickListener());
@@ -467,6 +558,7 @@ public class LCanvas extends Table{
setter.get(null);
mx = x;
my = y;
canvas.statements.updateJumpHeights = true;
return true;
}
@@ -487,6 +579,7 @@ public class LCanvas extends Table{
setter.get(null);
}
selecting = false;
canvas.statements.updateJumpHeights = true;
}
});
@@ -495,7 +588,7 @@ public class LCanvas extends Table{
setter.get(null);
}
setColor(listener.isOver() ? hoverColor : defaultColor);
setColor(listener.isOver() ? hoverColor : Color.white);
getStyle().imageUpColor = this.color;
});
@@ -509,22 +602,58 @@ public class LCanvas extends Table{
if(stage == null){
curve.remove();
}else{
canvas.jumps.addChild(curve);
canvas.statements.jumps.addChild(curve);
}
}
}
public static class JumpCurve extends Element{
public JumpButton button;
private boolean invertedHeight;
// for jump prediction; see DragLayout
public int predHeight = 0;
public boolean markedDone = false;
public int jumpUIBegin = 0, jumpUIEnd = 0;
public boolean flipped = false;
private float uiHeight = 60f;
public JumpCurve(JumpButton button){
this.button = button;
}
@Override
public void setSize(float width, float height){
if(height < 0){
y += height;
height = -height;
invertedHeight = true;
}
super.setSize(width, height);
}
@Override
public void act(float delta){
super.act(delta);
//MDTX(WayZer, 2024/8/6) Support Cull
invertedHeight = false;
Group desc = canvas.statements.jumps.parent;
Vec2 t = Tmp.v1.set(button.getWidth() / 2f, button.getHeight() / 2f);
button.localToAscendantCoordinates(desc, t);
setPosition(t.x, t.y);
Element hover = button.to.get() == null && button.selecting ? canvas.hovered : button.to.get();
if(hover != null){
t.set(hover.getWidth(), hover.getHeight() / 2f);
hover.localToAscendantCoordinates(desc, t);
setSize(t.x - x, t.y - y);
}else if(button.selecting){
setSize(button.mx, button.my);
}else{
setSize(0, 0);
}
if(button.listener.isOver()){
toFront();
}
@@ -532,71 +661,66 @@ public class LCanvas extends Table{
@Override
public void draw(){
canvas.jumpCount ++;
if(canvas.jumpCount > maxJumpsDrawn && !button.selecting && !button.listener.isOver()){
return;
}
Element hover = button.to.get() == null && button.selecting ? canvas.hovered : button.to.get();
boolean draw = false;
Vec2 t = Tmp.v1, r = Tmp.v2;
if(height == 0) return;
Vec2 t = Tmp.v1.set(width, !invertedHeight ? height : 0), r = Tmp.v2.set(0, !invertedHeight ? 0 : height);
Group desc = canvas.pane;
localToAscendantCoordinates(desc, r);
localToAscendantCoordinates(desc, t);
button.localToAscendantCoordinates(desc, r.set(0, 0));
drawCurve(r.x, r.y, t.x, t.y);
if(hover != null){
hover.localToAscendantCoordinates(desc, t.set(hover.getWidth(), hover.getHeight()/2f));
draw = true;
}else if(button.selecting){
t.set(r).add(button.mx, button.my);
draw = true;
}
float offset = canvas.pane.getVisualScrollY() - canvas.pane.getMaxY();
t.y += offset;
r.y += offset;
if(draw){
drawCurve(r.x + button.getWidth()/2f, r.y + button.getHeight()/2f, t.x, t.y);
float s = button.getWidth();
Draw.color(button.color);
Tex.logicNode.draw(t.x + s*0.75f, t.y - s/2f, -s, s);
Draw.reset();
}
float s = button.getWidth();
Draw.color(button.color, parentAlpha);
Tex.logicNode.draw(t.x + s * 0.75f, t.y - s / 2f, -s, s);
Draw.reset();
}
public void drawCurve(float x, float y, float x2, float y2){
Lines.stroke(4f, button.color);
Lines.stroke(Scl.scl(4f), button.color);
Draw.alpha(parentAlpha);
float dist = 100f;
//square jumps
if(false){
float len = Scl.scl(Mathf.randomSeed(hashCode(), 10, 50));
float maxX = Math.max(x, x2) + len;
// exponential smoothing
uiHeight = Mathf.lerp(
Scl.scl(Core.graphics.isPortrait() ? 20f : 40f) + Scl.scl(Core.graphics.isPortrait() ? 8f : 10f) * (float) predHeight,
uiHeight,
dynamicJumpHeights ? Mathf.pow(0.9f, Time.delta) : 0
);
//trapezoidal jumps
float dy = (y2 == y ? 0f : y2 > y ? 1f : -1f) * uiHeight * 0.5f;
//there's absolutely a better way to detect invalid trapezoids, but this probably isn't *that* slow and I don't care to fix it right now
if(Intersector.intersectSegments(x, y, x + uiHeight, y + dy, x2, y2, x + uiHeight, y2 - dy, Tmp.v3)){
Lines.beginLine();
Lines.linePoint(x, y);
Lines.linePoint(maxX, y);
Lines.linePoint(maxX, y2);
Lines.linePoint(Tmp.v3.x, Tmp.v3.y);
Lines.linePoint(x2, y2);
Lines.endLine();
}else{
Lines.beginLine();
Lines.linePoint(x, y);
Lines.linePoint(x + uiHeight, y + dy);
Lines.linePoint(x + uiHeight, y2 - dy);
Lines.linePoint(x2, y2);
Lines.endLine();
return;
}
}
Lines.curve(
x, y,
x + dist, y,
x2 + dist, y2,
x2, y2,
Math.max(18, (int)(Mathf.dst(x, y, x2, y2) / 6)));
public void prepareHeight(){
if(this.button.to.get() == null){
this.markedDone = true;
this.predHeight = 0;
this.flipped = false;
this.jumpUIBegin = this.jumpUIEnd = invalidJump;
}else{
this.markedDone = false;
int i = this.button.elem.index;
int j = this.button.to.get().index;
this.flipped = i >= j;
this.jumpUIBegin = Math.min(i,j);
this.jumpUIEnd = Math.max(i,j);
// height will be recalculated later
}
}
}
}

View File

@@ -61,6 +61,8 @@ public class LExecutor{
//yes, this is a minor memory leak, but it's probably not significant enough to matter
protected static IntFloatMap unitTimeouts = new IntFloatMap();
//lookup variable by name, lazy init.
protected @Nullable ObjectIntMap<String> nameMap;
static{
Events.on(ResetEvent.class, e -> unitTimeouts.clear());
@@ -92,6 +94,7 @@ public class LExecutor{
/** Loads with a specified assembler. Resets all variables. */
public void load(LAssembler builder){
nameMap = null;
vars = builder.vars.values().toSeq().retainAll(var -> !var.constant).toArray(LVar.class);
for(int i = 0; i < vars.length; i++){
vars[i].id = i;
@@ -106,6 +109,17 @@ public class LExecutor{
//region utility
public @Nullable LVar optionalVar(String name){
if(nameMap == null){
nameMap = new ObjectIntMap<>();
for(int i = 0; i < vars.length; i++){
nameMap.put(vars[i].name, i);
}
}
return optionalVar(nameMap.get(name, -1));
}
/** @return a Var from this processor. May be null if out of bounds. */
public @Nullable LVar optionalVar(int index){
return index < 0 || index >= vars.length ? null : vars[index];
@@ -557,6 +571,15 @@ public class LExecutor{
if(from instanceof MemoryBuild mem && (exec.privileged || (from.team == exec.team && !mem.block.privileged))){
output.setnum(address < 0 || address >= mem.memory.length ? 0 : mem.memory[address]);
}else if(from instanceof LogicBuild logic && (exec.privileged || (from.team == exec.team && !from.block.privileged)) && position.isobj && position.objval instanceof String name){
LVar fromVar = logic.executor.optionalVar(name);
if(fromVar != null && !output.constant){
output.objval = fromVar.objval;
output.numval = fromVar.numval;
output.isobj = fromVar.isobj;
}
}else if(target.isobj && target.objval instanceof CharSequence str){
output.setnum(address < 0 || address >= str.length() ? Double.NaN : (int)str.charAt(address));
}
}
}
@@ -580,6 +603,13 @@ public class LExecutor{
if(from instanceof MemoryBuild mem && (exec.privileged || (from.team == exec.team && !mem.block.privileged)) && address >= 0 && address < mem.memory.length){
mem.memory[address] = value.num();
}else if(from instanceof LogicBuild logic && (exec.privileged || (from.team == exec.team && !from.block.privileged)) && position.isobj && position.objval instanceof String name){
LVar toVar = logic.executor.optionalVar(name);
if(toVar != null && !toVar.constant){
toVar.objval = value.objval;
toVar.numval = value.numval;
toVar.isobj = value.isobj;
}
}
}
}
@@ -622,6 +652,10 @@ public class LExecutor{
}
}
}else{
if(target instanceof CharSequence seq && sense == LAccess.size){
to.setnum(seq.length());
return;
}
to.setobj(null);
}
}
@@ -984,6 +1018,29 @@ public class LExecutor{
}
}
public static class PrintCharI implements LInstruction{
public LVar value;
public PrintCharI(LVar value){
this.value = value;
}
PrintCharI(){}
@Override
public void run(LExecutor exec){
if(exec.textBuffer.length() >= maxTextBuffer) return;
if(value.isobj){
if(!(value.objval instanceof UnlockableContent cont)) return;
exec.textBuffer.append((char)cont.emojiChar());
return;
}
exec.textBuffer.append((char)Math.floor(value.numval));
}
}
public static class FormatI implements LInstruction{
public LVar value;
@@ -1376,10 +1433,10 @@ public class LExecutor{
Team t = team.team();
if(type.obj() instanceof UnitType type && !type.internal && !type.hidden && t != null && Units.canCreate(t, type)){
if(t != null && type.obj() instanceof UnitType type && !type.internal && Units.canCreate(t, type)){
//random offset to prevent stacking
var unit = type.spawn(t, World.unconv(x.numf()) + Mathf.range(0.01f), World.unconv(y.numf()) + Mathf.range(0.01f));
spawner.spawnEffect(unit, rotation.numf());
spawner.spawnEffect(unit);
result.setobj(unit);
}
}
@@ -1485,6 +1542,7 @@ public class LExecutor{
case dropZoneRadius -> state.rules.dropZoneRadius = value.numf() * 8f;
case unitCap -> state.rules.unitCap = Math.max(value.numi(), 0);
case lighting -> state.rules.lighting = value.bool();
case canGameOver -> state.rules.canGameOver = value.bool();
case mapArea -> {
int x = p1.numi(), y = p2.numi(), w = p3.numi(), h = p4.numi();
if(!checkMapArea(x, y, w, h, false)){
@@ -1511,7 +1569,7 @@ public class LExecutor{
state.rules.bannedUnits.remove(u);
}
}
case unitHealth, unitBuildSpeed, unitCost, unitDamage, blockHealth, blockDamage, buildSpeed, rtsMinSquad, rtsMinWeight -> {
case unitHealth, unitBuildSpeed, unitMineSpeed, unitCost, unitDamage, blockHealth, blockDamage, buildSpeed, rtsMinSquad, rtsMinWeight -> {
Team team = p1.team();
if(team != null){
float num = value.numf();
@@ -1519,6 +1577,7 @@ public class LExecutor{
case buildSpeed -> team.rules().buildSpeedMultiplier = Mathf.clamp(num, 0.001f, 50f);
case unitHealth -> team.rules().unitHealthMultiplier = Math.max(num, 0.001f);
case unitBuildSpeed -> team.rules().unitBuildSpeedMultiplier = Mathf.clamp(num, 0f, 50f);
case unitMineSpeed -> team.rules().unitMineSpeedMultiplier = Math.max(num, 0f);
case unitCost -> team.rules().unitCostMultiplier = Math.max(num, 0f);
case unitDamage -> team.rules().unitDamageMultiplier = Math.max(num, 0f);
case blockHealth -> team.rules().blockHealthMultiplier = Math.max(num, 0.001f);
@@ -1546,11 +1605,12 @@ public class LExecutor{
}else if(full){
//disable the rule, covers the whole map
if(set){
int prevX = state.rules.limitX, prevY = state.rules.limitY, prevW = state.rules.limitWidth, prevH = state.rules.limitHeight;
state.rules.limitMapArea = false;
if(!headless){
renderer.updateAllDarkness();
}
world.checkMapArea();
world.checkMapArea(prevX, prevY, prevW, prevH);
return false;
}
}
@@ -1559,12 +1619,20 @@ public class LExecutor{
}
if(set){
int prevX = state.rules.limitX, prevY = state.rules.limitY, prevW = state.rules.limitWidth, prevH = state.rules.limitHeight;
if(!state.rules.limitMapArea){
//it was never on in the first place, so the old bounds don't apply
prevW = 0;
prevH = 0;
prevX = -1;
prevY = -1;
}
state.rules.limitMapArea = true;
state.rules.limitX = x;
state.rules.limitY = y;
state.rules.limitWidth = w;
state.rules.limitHeight = h;
world.checkMapArea();
world.checkMapArea(prevX, prevY, prevW, prevH);
if(!headless){
renderer.updateAllDarkness();
@@ -1876,9 +1944,7 @@ public class LExecutor{
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
Vars.spawner.spawnEffect(unit);
spawner.spawnUnit(group, spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
}
}
}

View File

@@ -152,7 +152,13 @@ public class LParser{
}else{
//attempt parsing using custom parser if a match is found; this is for mods
if(LAssembler.customParsers.containsKey(tokens[0])){
statements.add(LAssembler.customParsers.get(tokens[0]).get(tokens));
var parsed = LAssembler.customParsers.get(tokens[0]).get(tokens);
if(!privileged && parsed != null && parsed.privileged()){
statements.add(new InvalidStatement());
}else{
statements.add(parsed);
}
}else{
//unparseable statement
statements.add(new InvalidStatement());

View File

@@ -19,6 +19,7 @@ import mindustry.logic.LExecutor.*;
import mindustry.logic.LogicFx.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -312,6 +313,46 @@ public class LStatements{
}
}
@RegisterStatement("printchar")
public static class PrintCharStatement extends LStatement{
public String value = "65";
@Override
public void build(Table table){
table.add(" char ");
TextField field = field(table, value, str -> value = str).get();
table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> showSelectTable(b, (t, hide) -> {
t.row();
t.table(i -> {
i.left();
int c = 0;
for(char j = 32; j < 127; j++){
final int chr = j;
i.button(String.valueOf(j), Styles.flatt, () -> {
value = Integer.toString(chr);
field.setText(value);
hide.run();
}).size(32f);
if(++c % 8 == 0) i.row();
}
});
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-2).color(table.color);
}
@Override
public LInstruction build(LAssembler builder){
return new PrintCharI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.io;
}
}
@RegisterStatement("format")
public static class FormatStatement extends LStatement{
public String value = "\"frog\"";
@@ -574,6 +615,29 @@ public class LStatements{
if(++c % 6 == 0) i.row();
}
}),
new Table(i -> {
i.left();
int c = 0;
for(UnitType item : Vars.content.units()){
if(!item.unlockedNow() || item.hidden) continue;
i.button(new TextureRegionDrawable(item.uiIcon), Styles.flati, iconSmall, () -> {
stype("@" + item.name);
hide.run();
}).size(40f);
if(++c % 6 == 0) i.row();
}
for(Block item : Vars.content.blocks()){
if(!item.unlockedNow() || item.isHidden()) continue;
i.button(new TextureRegionDrawable(item.uiIcon), Styles.flati, iconSmall, () -> {
stype("@" + item.name);
hide.run();
}).size(40f);
if(++c % 6 == 0) i.row();
}
}),
//sensors
new Table(i -> {
for(LAccess sensor : LAccess.senseable){
@@ -585,7 +649,7 @@ public class LStatements{
})
};
Drawable[] icons = {Icon.box, Icon.liquid, Icon.tree};
Drawable[] icons = {Icon.box, Icon.liquid, Icon.units, Icon.tree};
Stack stack = new Stack(tables[selected]);
ButtonGroup<Button> group = new ButtonGroup<>();
@@ -603,7 +667,7 @@ public class LStatements{
}).height(50f).growX().checked(selected == fi).group(group);
}
t.row();
t.add(stack).colspan(3).width(240f).left();
t.add(stack).colspan(4).width(240f).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
@@ -870,7 +934,7 @@ public class LStatements{
table.table(this::rebuild);
table.add().growX();
table.add(new JumpButton(() -> dest, s -> dest = s)).size(30).right().padLeft(-8);
table.add(new JumpButton(() -> dest, s -> dest = s, this.elem)).size(30).right().padLeft(-8);
String name = name();
@@ -1493,11 +1557,11 @@ public class LStatements{
table.add("natural ");
fields(table, natural, str -> natural = str);
table.add("x ").visible(() -> natural.equals("false"));
fields(table, x, str -> x = str).visible(() -> natural.equals("false"));
table.add("x ").visible(() -> !natural.equals("true"));
fields(table, x, str -> x = str).visible(() -> !natural.equals("true"));
table.add(" y ").visible(() -> natural.equals("false"));
fields(table, y, str -> y = str).visible(() -> natural.equals("false"));
table.add(" y ").visible(() -> !natural.equals("true"));
fields(table, y, str -> y = str).visible(() -> !natural.equals("true"));
}
@Override
@@ -1547,7 +1611,7 @@ public class LStatements{
fields(table, "w", p3, s -> p3 = s);
fields(table, "h", p4, s -> p4 = s);
}
case buildSpeed, unitHealth, unitBuildSpeed, unitCost, unitDamage, blockHealth, blockDamage, rtsMinSquad, rtsMinWeight -> {
case buildSpeed, unitHealth, unitBuildSpeed, unitMineSpeed, unitCost, unitDamage, blockHealth, blockDamage, rtsMinSquad, rtsMinWeight -> {
if(p1.equals("0")){
p1 = "@sharded";
}

View File

@@ -153,20 +153,27 @@ public class LogicDialog extends BaseDialog{
if(Core.graphics.isPortrait()) buttons.row();
buttons.button("@variables", Icon.menu, () -> {
//in the editor, it should display the global variables only (the button text is different)
if(!shouldShowVariables()){
globalsDialog.show();
return;
}
BaseDialog dialog = new BaseDialog("@variables");
dialog.hidden(() -> {
if(!wasPaused && !net.active()){
if(!wasPaused && !net.active() && !state.isMenu()){
state.set(State.paused);
}
});
dialog.shown(() -> {
if(!wasPaused && !net.active()){
if(!wasPaused && !net.active() && !state.isMenu()){
state.set(State.playing);
}
});
dialog.cont.pane(p -> {
p.margin(10f).marginRight(16f);
p.table(Tex.button, t -> {
t.defaults().fillX().height(45f);
@@ -220,11 +227,23 @@ public class LogicDialog extends BaseDialog{
dialog.buttons.button("@logic.globals", Icon.list, () -> globalsDialog.show()).size(210f, 64f);
dialog.show();
}).name("variables").disabled(b -> executor == null || executor.vars.length == 0);
}).name("variables").update(b -> {
if(shouldShowVariables()){
b.setText("@variables");
}else{
b.setText("@logic.globals");
}
});
buttons.button("@add", Icon.add, () -> {
showAddDialog();
}).disabled(t -> canvas.statements.getChildren().size >= LExecutor.maxInstructions);
Core.app.post(canvas::rebuild);
}
public boolean shouldShowVariables(){
return executor != null && executor.vars.length > 0 && !state.isMenu();
}
public void showAddDialog(){

View File

@@ -13,6 +13,7 @@ public enum LogicRule{
unitCap,
mapArea,
lighting,
canGameOver,
ambientLight,
solarMultiplier,
dragMultiplier,
@@ -23,6 +24,7 @@ public enum LogicRule{
buildSpeed,
unitHealth,
unitBuildSpeed,
unitMineSpeed,
unitCost,
unitDamage,
blockHealth,