diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index f11c906248..dbf2c9c30c 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -2302,7 +2302,8 @@ lst.getflag = Check if a global flag is set. lst.setprop = Sets a property of a unit or building. lst.effect = Create a particle effect. lst.sync = Sync a variable across the network.\nLimited to 20 times a second per variable. -lst.marker = Control markers displayed in world.\nCannot control markers created by objectives.\nEach marker created by this instruction has its own id. +lst.makemarker = Create a new logic marker in the world.\nAn ID to identify this marker must be provided.\nMarkers currently limited to 20,000 per world. +lst.setmarker = Set a property for a marker.\nThe ID used must be the same as in the Make Marker instruction. logic.nounitbuild = [red]Unit building logic is not allowed here. diff --git a/core/src/mindustry/core/GameState.java b/core/src/mindustry/core/GameState.java index 0e7e06436e..abad7d3899 100644 --- a/core/src/mindustry/core/GameState.java +++ b/core/src/mindustry/core/GameState.java @@ -1,9 +1,11 @@ package mindustry.core; import arc.*; +import arc.struct.*; import arc.util.*; import mindustry.game.EventType.*; import mindustry.game.*; +import mindustry.game.MapObjectives.*; import mindustry.gen.*; import mindustry.maps.*; import mindustry.type.*; @@ -32,6 +34,8 @@ public class GameState{ public Rules rules = new Rules(); /** Statistics for this save/game. Displayed after game over. */ public GameStats stats = new GameStats(); + /** Markers not linked to objectives. Controlled by world processors. */ + public IntMap markers = new IntMap<>(); /** Global attributes of the environment, calculated by weather. */ public Attributes envAttrs = new Attributes(); /** Team data. Gets reset every new game. */ diff --git a/core/src/mindustry/game/MapObjectives.java b/core/src/mindustry/game/MapObjectives.java index d15c50e511..33d9d8451d 100644 --- a/core/src/mindustry/game/MapObjectives.java +++ b/core/src/mindustry/game/MapObjectives.java @@ -31,6 +31,8 @@ import static mindustry.Vars.*; public class MapObjectives implements Iterable, Eachable{ public static final Seq> allObjectiveTypes = new Seq<>(); public static final Seq> allMarkerTypes = new Seq<>(); + public static final ObjectMap> markerNameToType = new ObjectMap<>(); + public static final Seq allMarkerTypeNanes = new Seq<>(); /** * All objectives the executor contains. Do not modify directly, ever! @@ -72,8 +74,9 @@ public class MapObjectives implements Iterable, Eachable type = prov.get().getClass(); - JsonIO.classTag(Strings.camelize(type.getSimpleName().replace("Objective", "")), type); - JsonIO.classTag(type.getSimpleName().replace("Objective", ""), type); + String name = type.getSimpleName().replace("Objective", ""); + JsonIO.classTag(Strings.camelize(name), type); + JsonIO.classTag(name, type); } } @@ -83,8 +86,12 @@ public class MapObjectives implements Iterable, Eachable type = prov.get().getClass(); - JsonIO.classTag(Strings.camelize(type.getSimpleName().replace("Marker", "")), type); - JsonIO.classTag(type.getSimpleName().replace("Marker", ""), type); + String name = type.getSimpleName().replace("Marker", ""); + allMarkerTypeNanes.add(Strings.camelize(name)); + markerNameToType.put(name, prov); + markerNameToType.put(Strings.camelize(name), prov); + JsonIO.classTag(Strings.camelize(name), type); + JsonIO.classTag(name, type); } } diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index 5e6905b3ee..ede6161f49 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -7,7 +7,6 @@ import arc.util.serialization.*; import arc.util.serialization.Json.*; import mindustry.*; import mindustry.content.*; -import mindustry.game.MapObjectives.*; import mindustry.graphics.g3d.*; import mindustry.io.*; import mindustry.type.*; @@ -146,8 +145,6 @@ public class Rules{ public MapObjectives objectives = new MapObjectives(); /** Flags set by objectives. Used in world processors. */ public ObjectSet objectiveFlags = new ObjectSet<>(); - /** Markers not linked to objectives. Controlled by world processors. */ - public IntMap markers = new IntMap<>(); /** If true, fog of war is enabled. Enemy units and buildings are hidden unless in radar view. */ public boolean fog = false; /** If fog = true, this is whether static (black) fog is enabled. */ diff --git a/core/src/mindustry/graphics/MinimapRenderer.java b/core/src/mindustry/graphics/MinimapRenderer.java index 1054e1e788..7d9503a82a 100644 --- a/core/src/mindustry/graphics/MinimapRenderer.java +++ b/core/src/mindustry/graphics/MinimapRenderer.java @@ -259,8 +259,8 @@ public class MinimapRenderer{ } }); - for(var marker : state.rules.markers.values()){ - if(marker != null)marker.drawMinimap(this); + for(var marker : state.markers.values()){ + if(marker != null) marker.drawMinimap(this); } } diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index 8ba4559fca..be2b47db93 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -118,8 +118,8 @@ public class OverlayRenderer{ for(var marker : obj.markers) marker.draw(); }); - for(var marker : state.rules.markers.values()){ - if(marker != null)marker.draw(); + for(var marker : state.markers.values()){ + if(marker != null) marker.draw(); } if(player.dead()) return; //dead players don't draw diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java index 3a8318fb2b..e301f5b499 100644 --- a/core/src/mindustry/io/JsonIO.java +++ b/core/src/mindustry/io/JsonIO.java @@ -44,6 +44,15 @@ public class JsonIO{ } }; + public static void writeBytes(Object value, Class elementType, DataOutputStream output){ + json.setWriter(new UBJsonWriter(output)); + json.writeValue(value, value == null ? null : value.getClass(), elementType); + } + + public static T readBytes(Class type, Class elementType, DataInputStream input) throws IOException{ + return json.readValue(type, elementType, new UBJsonReader().parseWihoutClosing(input)); + } + public static String write(Object object){ return json.toJson(object, object.getClass()); } diff --git a/core/src/mindustry/io/SaveIO.java b/core/src/mindustry/io/SaveIO.java index 41888cbb41..3ba9a38aa6 100644 --- a/core/src/mindustry/io/SaveIO.java +++ b/core/src/mindustry/io/SaveIO.java @@ -20,7 +20,7 @@ public class SaveIO{ /** Save format header. */ public static final byte[] header = {'M', 'S', 'A', 'V'}; public static final IntMap versions = new IntMap<>(); - public static final Seq versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7()); + public static final Seq versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7(), new Save8()); static{ for(SaveVersion version : versionArray){ diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java index 3b1fcb1996..30cefcfc1f 100644 --- a/core/src/mindustry/io/SaveVersion.java +++ b/core/src/mindustry/io/SaveVersion.java @@ -6,12 +6,14 @@ import arc.math.geom.*; import arc.struct.*; import arc.util.*; import arc.util.io.*; +import mindustry.*; import mindustry.content.*; import mindustry.content.TechTree.*; import mindustry.core.*; import mindustry.ctype.*; import mindustry.entities.*; import mindustry.game.*; +import mindustry.game.MapObjectives.*; import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.maps.Map; @@ -74,6 +76,7 @@ public abstract class SaveVersion extends SaveFileReader{ try{ region("map", stream, counter, in -> readMap(in, context)); region("entities", stream, counter, this::readEntities); + if(version >= 8) region("markers", stream, counter, this::readMarkers); region("custom", stream, counter, this::readCustomChunks); }finally{ content.setTemporaryMapper(null); @@ -85,6 +88,7 @@ public abstract class SaveVersion extends SaveFileReader{ region("content", stream, this::writeContentHeader); region("map", stream, this::writeMap); region("entities", stream, this::writeEntities); + region("markers", stream, this::writeMarkers); region("custom", stream, s -> writeCustomChunks(s, false)); } @@ -395,6 +399,14 @@ public abstract class SaveVersion extends SaveFileReader{ writeWorldEntities(stream); } + public void writeMarkers(DataOutput stream) throws IOException{ + JsonIO.writeBytes(Vars.state.markers, ObjectiveMarker.class, (DataOutputStream)stream); + } + + public void readMarkers(DataInput stream) throws IOException{ + Vars.state.markers = JsonIO.readBytes(IntMap.class, ObjectiveMarker.class, (DataInputStream)stream); + } + public void readTeamBlocks(DataInput stream) throws IOException{ int teamc = stream.readInt(); diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index b0c1aba83e..00bf1a45da 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -6,7 +6,6 @@ import arc.math.geom.*; import arc.struct.*; import arc.util.*; import arc.util.io.*; -import arc.util.serialization.*; import mindustry.ai.*; import mindustry.ai.types.*; import mindustry.annotations.Annotations.*; @@ -631,6 +630,14 @@ public class TypeIO{ return KickReason.values()[read.b()]; } + public static void writeMarkerControl(Writes write, LMarkerControl reason){ + write.b((byte)reason.ordinal()); + } + + public static LMarkerControl readMarkerControl(Reads read){ + return LMarkerControl.all[read.ub()]; + } + public static void writeRules(Writes write, Rules rules){ String string = JsonIO.write(rules); byte[] bytes = string.getBytes(charset); diff --git a/core/src/mindustry/io/versions/Save8.java b/core/src/mindustry/io/versions/Save8.java new file mode 100644 index 0000000000..8dc1c0ca01 --- /dev/null +++ b/core/src/mindustry/io/versions/Save8.java @@ -0,0 +1,10 @@ +package mindustry.io.versions; + +import mindustry.io.*; + +public class Save8 extends SaveVersion{ + + public Save8(){ + super(8); + } +} diff --git a/core/src/mindustry/logic/GlobalVars.java b/core/src/mindustry/logic/GlobalVars.java index 57b30bcd7b..ca0bead619 100644 --- a/core/src/mindustry/logic/GlobalVars.java +++ b/core/src/mindustry/logic/GlobalVars.java @@ -27,7 +27,7 @@ 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; + private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varServer, varClient; private ObjectIntMap namesToIds = new ObjectIntMap<>(); private Seq vars = new Seq<>(Var.class); @@ -57,6 +57,7 @@ public class GlobalVars{ varWaveTime = put("@waveTime", 0); varServer = put("@server", 0); + varClient = put("@server", 0); //special enums put("@ctrlProcessor", ctrlProcessor); @@ -152,6 +153,7 @@ public class GlobalVars{ //network vars.items[varServer].numval = (net.server() || !net.active()) ? 1 : 0; + vars.items[varServer].numval = net.client() ? 1 : 0; } /** @return a piece of content based on its logic ID. This is not equivalent to content ID. */ diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 4856a5668a..e51d603287 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -1842,11 +1842,11 @@ public class LExecutor{ } } - public static class MarkerI implements LInstruction{ - public LMarkerControl type = LMarkerControl.create; + public static class SetMarkerI implements LInstruction{ + public LMarkerControl type = LMarkerControl.x; public int id, p1, p2, p3; - public MarkerI(LMarkerControl type, int id, int p1, int p2, int p3){ + public SetMarkerI(LMarkerControl type, int id, int p1, int p2, int p3){ this.type = type; this.id = id; this.p1 = p1; @@ -1854,50 +1854,82 @@ public class LExecutor{ this.p3 = p3; } - public MarkerI(){ + public SetMarkerI(){ } @Override public void run(LExecutor exec){ - ObjectiveMarker marker = null; - - if(type == LMarkerControl.create){ - int type = exec.numi(p1); - if(0 <= type && type < MapObjectives.allMarkerTypes.size){ - marker = MapObjectives.allMarkerTypes.get(type).get(); - if(marker != null){ - state.rules.markers.put(exec.numi(id), marker); - marker.control(LMarkerControl.pos, exec.num(p2), exec.num(p3), 0); - } - } - return; - }else if(type == LMarkerControl.remove){ - state.rules.markers.remove(exec.numi(id)); + if(type == LMarkerControl.remove){ + state.markers.remove(exec.numi(id)); }else{ - marker = state.rules.markers.get(exec.numi(id)); - } + if(!state.markers.containsKey(exec.numi(id))) return; - if(marker == null) return; - - Var var = exec.var(p1); - if(!var.isobj){ - marker.control(type, exec.num(p1), exec.num(p2), exec.num(p3)); - }else{ if(type == LMarkerControl.text){ - marker.setText((exec.obj(p1) != null ? exec.obj(p1).toString() : "null"), true); + Call.updateMarkerText(exec.numi(id), type, (exec.obj(p1) != null ? exec.obj(p1).toString() : "null")); }else if(type == LMarkerControl.flushText){ - marker.setText(exec.textBuffer.toString(), false); + Call.updateMarkerText(exec.numi(id), type, exec.textBuffer.toString()); exec.textBuffer.setLength(0); + }else{ + Call.updateMarker(exec.numi(id), type, exec.num(p1), exec.num(p2), exec.num(p3)); } } + } + } - Call.setMarker(exec.numi(id), marker); + public static class MakeMarkerI implements LInstruction{ + //TODO arbitrary number + public static final int maxMarkers = 20000; + + public String type = "shape"; + public int id, x, y; + + public MakeMarkerI(String type, int id, int x, int y){ + this.type = type; + this.id = id; + this.x = x; + this.y = y; + } + + public MakeMarkerI(){ + } + + @Override + public void run(LExecutor exec){ + var cons = MapObjectives.markerNameToType.get(type); + + if(cons != null && !net.client() && state.markers.size < maxMarkers){ + //TODO max markers... + var marker = cons.get(); + marker.control(LMarkerControl.pos, exec.num(x), exec.num(y), 0); + + Call.createMarker(exec.numi(id), marker); + } } } @Remote(called = Loc.server, variants = Variant.both, unreliable = true) - public static void setMarker(int id, ObjectiveMarker marker){ - state.rules.markers.put(id, marker); + public static void createMarker(int id, ObjectiveMarker marker){ + state.markers.put(id, marker); + } + + @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, String text){ + var marker = state.markers.get(id); + if(marker != null){ + if(type == LMarkerControl.text){ + marker.setText(text, true); + }else if(type == LMarkerControl.flushText){ + marker.setText(text, false); + } + } } //endregion diff --git a/core/src/mindustry/logic/LMarkerControl.java b/core/src/mindustry/logic/LMarkerControl.java index a3726864b3..f0ae6ae6b1 100644 --- a/core/src/mindustry/logic/LMarkerControl.java +++ b/core/src/mindustry/logic/LMarkerControl.java @@ -1,7 +1,6 @@ package mindustry.logic; public enum LMarkerControl{ - create("type", "x", "y"), remove, setVisibility("true/false"), toggleVisibility, diff --git a/core/src/mindustry/logic/LStatement.java b/core/src/mindustry/logic/LStatement.java index e16830677d..3117e91e29 100644 --- a/core/src/mindustry/logic/LStatement.java +++ b/core/src/mindustry/logic/LStatement.java @@ -108,6 +108,18 @@ 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 fieldst(Table table, String desc, String value, Cons 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]; + } + protected Cell fields(Table table, String value, Cons setter){ return field(table, value, setter).width(85f); } diff --git a/core/src/mindustry/logic/LStatements.java b/core/src/mindustry/logic/LStatements.java index 9d44cd24c7..f3e1a608fa 100644 --- a/core/src/mindustry/logic/LStatements.java +++ b/core/src/mindustry/logic/LStatements.java @@ -1921,9 +1921,9 @@ public class LStatements{ } } - @RegisterStatement("marker") - public static class MarkerStatement extends LStatement{ - public LMarkerControl type = LMarkerControl.create; + @RegisterStatement("setmarker") + public static class SetMarkerStatement extends LStatement{ + public LMarkerControl type = LMarkerControl.x; public String id = "0", p1 = "0", p2 = "0", p3 = "0"; @Override @@ -1934,6 +1934,8 @@ public class LStatements{ void rebuild(Table table){ table.clearChildren(); + table.add("set"); + table.button(b -> { b.label(() -> type.name()); b.clicked(() -> showSelect(b, LMarkerControl.all, type, t -> { @@ -1942,48 +1944,23 @@ public class LStatements{ }, 2, cell -> cell.size(140, 50))); }, Styles.logict, () -> {}).size(190, 40).color(table.color).left().padLeft(2); - table.add(" marker# "); + row(table); - fields(table, id, str -> id = str); + fieldst(table, "of id#", id, str -> id = str); - if(type == LMarkerControl.create){ - fields(table, type.params[0], p1, v -> p1 = v).padRight(0f).left(); - table.button(b -> { - b.image(Icon.pencilSmall); - b.clicked(() -> showSelectTable(b, (t, hide) -> { - t.row(); - t.table(i -> { - i.left(); - int c = 0; - for(var gen : MapObjectives.allMarkerTypes){ - var marker = gen.get(); - i.button(marker.typeName(), Styles.flatt, () -> { - p1 = Integer.toString(MapObjectives.allMarkerTypes.indexOf(gen)); - rebuild(table); - hide.run(); - }).size(140, 40); + //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; - if(++c % 2 == 0) i.row(); - } - }).colspan(3).width(280f).left(); - })); - }, Styles.logict, () -> {}).size(40f).padLeft(-2).color(table.color); - fields(table, type.params[1], p2, v -> p2 = v); - fields(table, type.params[2], p3, v -> p3 = v); - }else{ - //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 - int c = 0; - for(int i = 0; i < type.params.length; i++){ + table.table(t -> { + t.setColor(table.color); - fields(table, type.params[i], i == 0 ? p1 : i == 1 ? p2 : p3, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : v -> p3 = v).width(100f); + fields(t, type.params[i], i == 0 ? p1 : i == 1 ? p2 : p3, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : v -> p3 = v).width(100f); + }); - if(++c % 2 == 0) row(table); - - if(i == 3){ - table.row(); - } - } + if(i == 0) row(table); + if(i == 2) table.row(); } } @@ -1994,7 +1971,49 @@ public class LStatements{ @Override public LInstruction build(LAssembler builder){ - return new MarkerI(type, builder.var(id), builder.var(p1), builder.var(p2), builder.var(p3)); + 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 id = "0", type = "Shape", x = "0", y = "0"; + + @Override + public void build(Table table){ + table.clearChildren(); + + table.button(b -> { + b.label(() -> type); + + b.clicked(() -> showSelect(b, MapObjectives.allMarkerTypeNanes.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); + } + + @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)); } @Override diff --git a/core/src/mindustry/net/NetworkIO.java b/core/src/mindustry/net/NetworkIO.java index c6b2ce1478..fdc223184c 100644 --- a/core/src/mindustry/net/NetworkIO.java +++ b/core/src/mindustry/net/NetworkIO.java @@ -50,6 +50,7 @@ public class NetworkIO{ SaveIO.getSaveWriter().writeContentHeader(stream); SaveIO.getSaveWriter().writeMap(stream); SaveIO.getSaveWriter().writeTeamBlocks(stream); + SaveIO.getSaveWriter().writeMarkers(stream); SaveIO.getSaveWriter().writeCustomChunks(stream, true); }catch(IOException e){ throw new RuntimeException(e); @@ -81,6 +82,7 @@ public class NetworkIO{ SaveIO.getSaveWriter().readContentHeader(stream); SaveIO.getSaveWriter().readMap(stream, world.context); SaveIO.getSaveWriter().readTeamBlocks(stream); + SaveIO.getSaveWriter().readMarkers(stream); SaveIO.getSaveWriter().readCustomChunks(stream); }catch(IOException e){ throw new RuntimeException(e); diff --git a/gradle.properties b/gradle.properties index 06ada9c2f6..70ba79f907 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=443db6a9c5 +archash=8deb453b78