From 066e1b39abc038ec1f555af140bcd2e376cd0dab Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Wed, 8 Apr 2026 17:40:03 +0200 Subject: [PATCH 1/7] Adding a map objective copy/paste through the JSON copy mechanism --- .../mindustry/editor/MapObjectivesCanvas.java | 10 ++++++++++ .../mindustry/editor/MapObjectivesDialog.java | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/core/src/mindustry/editor/MapObjectivesCanvas.java b/core/src/mindustry/editor/MapObjectivesCanvas.java index 48c424281b..f62456d949 100644 --- a/core/src/mindustry/editor/MapObjectivesCanvas.java +++ b/core/src/mindustry/editor/MapObjectivesCanvas.java @@ -29,6 +29,7 @@ public class MapObjectivesCanvas extends WidgetGroup{ public final float unitSize = Scl.scl(48f); public Seq objectives = new Seq<>(); + public int pasteX, pasteY; public ObjectiveTilemap tilemap; protected MapObjective query; @@ -314,6 +315,11 @@ public class MapObjectivesCanvas extends WidgetGroup{ public boolean moveTile(ObjectiveTile tile, int newX, int newY){ if(!validPlace(newX, newY, tile)) return false; + if(tile.obj.editorX == pasteX && tile.obj.editorY == pasteY){ + pasteX = newX; + pasteY = newY; + } + tile.pos(newX, newY); return true; @@ -381,6 +387,10 @@ public class MapObjectivesCanvas extends WidgetGroup{ dialog.addCloseButton(); dialog.show(); }); + b.button(Icon.copySmall, () -> { + pasteX = obj.editorX; + pasteY = obj.editorY; + }); // Object position can be used as an ID. b.button(Icon.trashSmall, () -> removeTile(this)); }).left().grow(); }).growX().height(unitSize / Scl.scl(1f) * 2).get().addCaptureListener(mover = new Mover()); diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index ae82444014..57d731dbf9 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -39,6 +39,8 @@ public class MapObjectivesDialog extends BaseDialog{ /** Maps annotation type with its field parsers. Non-annotated fields are mapped with {@link Override}. */ private static final ObjectMap, ObjectMap, FieldInterpreter>> interpreters = new ObjectMap<>(); + private MapObjective copy = null; + static{ // Default un-annotated field interpreters. setProvider(String.class, (type, cons) -> cons.get("")); @@ -486,6 +488,20 @@ public class MapObjectivesDialog extends BaseDialog{ buttons.defaults().size(160f, 64f).pad(2f); buttons.button("@back", Icon.left, MapObjectivesDialog.this::hide); buttons.button("@add", Icon.add, () -> getProvider(MapObjective.class).get(new TypeInfo(MapObjective.class), canvas::query)); + buttons.button("@mod.dependencies.download", Icon.paste, () -> { + Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); + for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ + if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ + copy = obj; + } + }; + if(copy == null){ + ui.showErrorMessage("@save.import.invalid"); + }else{ + canvas.tilemap.createTile(copy.editorX, copy.editorY, copy, true); + canvas.objectives.add(copy); + } + }); buttons.button("@waves.edit", Icon.edit, () -> { BaseDialog dialog = new BaseDialog("@waves.edit"); dialog.addCloseButton(); From fe5ca2614e17a20f67d38613c90533eba112cc11 Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Wed, 8 Apr 2026 17:54:24 +0200 Subject: [PATCH 2/7] Adding myself as a contributor if my pr is good enough --- core/assets/contributors | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/assets/contributors b/core/assets/contributors index ce002e134b..b21d5b2632 100644 --- a/core/assets/contributors +++ b/core/assets/contributors @@ -190,4 +190,5 @@ buj Agzam4 ItsKirby69 TheCuber -Jovinull \ No newline at end of file +Jovinull +Adleanogare \ No newline at end of file From 02ce0bc8a56cd76397a8629e7b45cca3a7c09479 Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Wed, 8 Apr 2026 18:35:43 +0200 Subject: [PATCH 3/7] Slight change to text --- core/assets/bundles/bundle.properties | 2 ++ core/src/mindustry/editor/MapObjectivesDialog.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 1e08297ceb..e125fe06e8 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -574,6 +574,7 @@ editor.randomize = Randomize editor.moveup = Move Up editor.movedown = Move Down editor.copy = Copy +editor.paste = Paste editor.apply = Apply editor.generate = Generate editor.sectorgenerate = Sector Generate @@ -802,6 +803,7 @@ error.io = Network I/O error. error.any = Unknown network error. error.bloom = Failed to initialize bloom.\nYour device may not support it. error.moddex = Mindustry is unable to load this mod.\nYour device is blocking import of Java mods due to recent changes in Android.\nThis will not be fixed. There is no known workaround for this issue. +error.objectivecopy = No copied objective. weather.rain.name = Rain weather.snowing.name = Snow diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index 57d731dbf9..9277dca9da 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -488,7 +488,7 @@ public class MapObjectivesDialog extends BaseDialog{ buttons.defaults().size(160f, 64f).pad(2f); buttons.button("@back", Icon.left, MapObjectivesDialog.this::hide); buttons.button("@add", Icon.add, () -> getProvider(MapObjective.class).get(new TypeInfo(MapObjective.class), canvas::query)); - buttons.button("@mod.dependencies.download", Icon.paste, () -> { + buttons.button("@editor.paste", Icon.paste, () -> { Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ @@ -496,7 +496,7 @@ public class MapObjectivesDialog extends BaseDialog{ } }; if(copy == null){ - ui.showErrorMessage("@save.import.invalid"); + ui.showErrorMessage("@error.objectivecopy"); }else{ canvas.tilemap.createTile(copy.editorX, copy.editorY, copy, true); canvas.objectives.add(copy); From a5d076deccd415e6508b6685b754029bfb231148 Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Mon, 13 Apr 2026 18:13:32 +0200 Subject: [PATCH 4/7] The copy blocks now only appear for mobile. (Stilled need to be compilled and tested, desktop ctrl-c ctrl-v will come soon.) --- .../mindustry/editor/MapObjectivesCanvas.java | 14 ++++++---- .../mindustry/editor/MapObjectivesDialog.java | 28 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/core/src/mindustry/editor/MapObjectivesCanvas.java b/core/src/mindustry/editor/MapObjectivesCanvas.java index f62456d949..fa884c6abf 100644 --- a/core/src/mindustry/editor/MapObjectivesCanvas.java +++ b/core/src/mindustry/editor/MapObjectivesCanvas.java @@ -29,7 +29,7 @@ public class MapObjectivesCanvas extends WidgetGroup{ public final float unitSize = Scl.scl(48f); public Seq objectives = new Seq<>(); - public int pasteX, pasteY; + public int pasteX = -999, pasteY = -999; public ObjectiveTilemap tilemap; protected MapObjective query; @@ -387,10 +387,14 @@ public class MapObjectivesCanvas extends WidgetGroup{ dialog.addCloseButton(); dialog.show(); }); - b.button(Icon.copySmall, () -> { - pasteX = obj.editorX; - pasteY = obj.editorY; - }); // Object position can be used as an ID. + + if(mobile){ + b.button(Icon.copySmall, () -> { + pasteX = obj.editorX; + pasteY = obj.editorY; + }); // Object position can be used as an ID. + } + b.button(Icon.trashSmall, () -> removeTile(this)); }).left().grow(); }).growX().height(unitSize / Scl.scl(1f) * 2).get().addCaptureListener(mover = new Mover()); diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index 9277dca9da..bc239643cf 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -488,20 +488,6 @@ public class MapObjectivesDialog extends BaseDialog{ buttons.defaults().size(160f, 64f).pad(2f); buttons.button("@back", Icon.left, MapObjectivesDialog.this::hide); buttons.button("@add", Icon.add, () -> getProvider(MapObjective.class).get(new TypeInfo(MapObjective.class), canvas::query)); - buttons.button("@editor.paste", Icon.paste, () -> { - Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); - for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ - if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ - copy = obj; - } - }; - if(copy == null){ - ui.showErrorMessage("@error.objectivecopy"); - }else{ - canvas.tilemap.createTile(copy.editorX, copy.editorY, copy, true); - canvas.objectives.add(copy); - } - }); buttons.button("@waves.edit", Icon.edit, () -> { BaseDialog dialog = new BaseDialog("@waves.edit"); dialog.addCloseButton(); @@ -536,6 +522,20 @@ public class MapObjectivesDialog extends BaseDialog{ }); if(mobile){ + buttons.button("@editor.paste", Icon.paste, () -> { + Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); + for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ + if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ + copy = obj; + } + }; + if(copy == null){ + ui.showErrorMessage("@error.objectivecopy"); + }else{ + canvas.tilemap.createTile(copy.editorX, copy.editorY, copy, true); + canvas.objectives.add(copy); + } + }).visible(() -> copy != null || canvas.pasteX != -999 || canvas.pasteY != -999); buttons.button("@cancel", Icon.cancel, canvas::stopQuery).visible(() -> canvas.isQuerying()); buttons.button("@ok", Icon.ok, canvas::placeQuery).visible(() -> canvas.isQuerying()); } From 21d6494923972f91d27b7ff66a6bcb841dd5815e Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Sat, 18 Apr 2026 16:20:25 +0200 Subject: [PATCH 5/7] Adding the ctrl-c ctrl-v copy for keyboard device. The behaviour changed in some small ways. --- core/assets/bundles/bundle.properties | 2 +- .../mindustry/editor/MapObjectivesDialog.java | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index e125fe06e8..fc82f9bfbd 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -803,7 +803,7 @@ error.io = Network I/O error. error.any = Unknown network error. error.bloom = Failed to initialize bloom.\nYour device may not support it. error.moddex = Mindustry is unable to load this mod.\nYour device is blocking import of Java mods due to recent changes in Android.\nThis will not be fixed. There is no known workaround for this issue. -error.objectivecopy = No copied objective. +error.objectivecopy = Invalid copy. weather.rain.name = Rain weather.snowing.name = Snow diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index bc239643cf..10bb2b2dbf 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -3,6 +3,8 @@ package mindustry.editor; import arc.*; import arc.func.*; import arc.graphics.*; +import arc.input.*; +import arc.math.*; import arc.math.geom.*; import arc.scene.event.*; import arc.scene.ui.*; @@ -14,6 +16,7 @@ import mindustry.content.*; import mindustry.ctype.*; import mindustry.game.*; import mindustry.game.MapObjectives.*; +import mindustry.editor.MapObjectivesCanvas.ObjectiveTilemap.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.io.*; @@ -554,6 +557,58 @@ public class MapObjectivesDialog extends BaseDialog{ out.get(canvas.objectives); out = arr -> {}; }); + + update(() -> { + if(hasKeyboard()){ + doInput(); + } + }); + } + + private void doInput(){ + if(Core.input.keyDown(KeyCode.controlLeft) || Core.input.keyDown(KeyCode.controlRight)){ + if(Core.input.keyTap(KeyCode.c)){ + Vec2 pos = screenToLocalCoordinates(Core.input.mouse()); + canvas.pasteX = Mathf.round((pos.x - canvas.objWidth * canvas.unitSize / 2f) / canvas.unitSize); + canvas.pasteY = Mathf.floor((pos.y - canvas.unitSize) / canvas.unitSize); + boolean copied = false; + Tmp.r1.set(canvas.pasteX, canvas.pasteY, 1, 1).grow(-0.001f); + for(var obj : canvas.objectives){ + if(Tmp.r2.set(obj.editorX - 2, obj.editorY - 1, canvas.objWidth, canvas.objHeight).overlaps(Tmp.r1)){ + copied = true; + canvas.pasteX = obj.editorX; + canvas.pasteY = obj.editorY; + } + } + + if(!copied){ + ui.showErrorMessage("@error.objectivecopy"); + } + } + } + + if(Core.input.keyDown(KeyCode.controlLeft) || Core.input.keyDown(KeyCode.controlRight)){ + if(Core.input.keyTap(KeyCode.v)){ + copy = null; + Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); + for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ + if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ + copy = obj; + } + }; + Vec2 pos = screenToLocalCoordinates(Core.input.mouse()); + int tx = Mathf.round((pos.x - canvas.objWidth * canvas.unitSize / 2f) / canvas.unitSize); + int ty = Mathf.floor((pos.y - canvas.unitSize) / canvas.unitSize); + if(copy == null || !canvas.tilemap.validPlace(tx, ty, null)){ + ui.showErrorMessage("@error.objectivecopy"); + }else{ + copy.editorX = tx; + copy.editorY = ty; + canvas.tilemap.createTile(tx, ty, copy, true); + canvas.objectives.add(copy); + } + } + } } public void show(Seq objectives, Cons> out){ From 3b4c78ca4ab4f973810dd0159e6886a076f728e4 Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Sat, 18 Apr 2026 22:54:27 +0200 Subject: [PATCH 6/7] Patched a bug where when changing editor you could still copy a previous objective, leading to reference issue --- core/src/mindustry/editor/MapObjectivesDialog.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index 10bb2b2dbf..4cc001b987 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -42,8 +42,6 @@ public class MapObjectivesDialog extends BaseDialog{ /** Maps annotation type with its field parsers. Non-annotated fields are mapped with {@link Override}. */ private static final ObjectMap, ObjectMap, FieldInterpreter>> interpreters = new ObjectMap<>(); - private MapObjective copy = null; - static{ // Default un-annotated field interpreters. setProvider(String.class, (type, cons) -> cons.get("")); @@ -526,6 +524,7 @@ public class MapObjectivesDialog extends BaseDialog{ if(mobile){ buttons.button("@editor.paste", Icon.paste, () -> { + MapObjective copy = null; Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ @@ -589,7 +588,7 @@ public class MapObjectivesDialog extends BaseDialog{ if(Core.input.keyDown(KeyCode.controlLeft) || Core.input.keyDown(KeyCode.controlRight)){ if(Core.input.keyTap(KeyCode.v)){ - copy = null; + MapObjective copy = null; Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ From 7f7117187e1c97d3a310079bdf03a7ed53c3a06a Mon Sep 17 00:00:00 2001 From: Arthur Schifano Date: Sat, 18 Apr 2026 22:57:50 +0200 Subject: [PATCH 7/7] The global variable was still needed, for the button. --- core/src/mindustry/editor/MapObjectivesDialog.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/mindustry/editor/MapObjectivesDialog.java b/core/src/mindustry/editor/MapObjectivesDialog.java index 4cc001b987..30264d5774 100644 --- a/core/src/mindustry/editor/MapObjectivesDialog.java +++ b/core/src/mindustry/editor/MapObjectivesDialog.java @@ -42,6 +42,8 @@ public class MapObjectivesDialog extends BaseDialog{ /** Maps annotation type with its field parsers. Non-annotated fields are mapped with {@link Override}. */ private static final ObjectMap, ObjectMap, FieldInterpreter>> interpreters = new ObjectMap<>(); + private MapObjective copy = null; + static{ // Default un-annotated field interpreters. setProvider(String.class, (type, cons) -> cons.get("")); @@ -524,7 +526,7 @@ public class MapObjectivesDialog extends BaseDialog{ if(mobile){ buttons.button("@editor.paste", Icon.paste, () -> { - MapObjective copy = null; + copy = null; Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){ @@ -588,7 +590,7 @@ public class MapObjectivesDialog extends BaseDialog{ if(Core.input.keyDown(KeyCode.controlLeft) || Core.input.keyDown(KeyCode.controlRight)){ if(Core.input.keyTap(KeyCode.v)){ - MapObjective copy = null; + copy = null; Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives))); for(var obj : JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all){ if(obj.editorX == canvas.pasteX && obj.editorY == canvas.pasteY){