From 15ca6721792d699830d537715dafc2293be1b8aa Mon Sep 17 00:00:00 2001
From: Anuken <arnukren@gmail.com>
Date: Thu, 3 Oct 2024 14:44:33 -0400
Subject: [PATCH] In-game editor UI improvements

---
 .../sprites/blocks/environment/remove-ore.png | Bin 0 -> 496 bytes
 .../blocks/environment/remove-wall.png        | Bin 0 -> 253 bytes
 core/assets/bundles/bundle.properties         |   2 +
 core/assets/icons/icons.properties            |   2 +
 core/src/mindustry/content/Blocks.java        |   6 +-
 .../src/mindustry/editor/MapEditorDialog.java |   8 +-
 core/src/mindustry/input/MobileInput.java     |  12 +-
 core/src/mindustry/net/CrashHandler.java      |   2 +-
 .../mindustry/ui/fragments/HudFragment.java   | 104 ++++++++++++++++--
 core/src/mindustry/world/Block.java           |   5 +
 core/src/mindustry/world/Build.java           |   8 +-
 .../world/blocks/ConstructBlock.java          |   2 +
 .../world/blocks/environment/RemoveOre.java   |  51 +++++++++
 .../world/blocks/environment/RemoveWall.java  |  50 +++++++++
 gradle.properties                             |   2 +-
 15 files changed, 230 insertions(+), 24 deletions(-)
 create mode 100644 core/assets-raw/sprites/blocks/environment/remove-ore.png
 create mode 100644 core/assets-raw/sprites/blocks/environment/remove-wall.png
 create mode 100644 core/src/mindustry/world/blocks/environment/RemoveOre.java
 create mode 100644 core/src/mindustry/world/blocks/environment/RemoveWall.java

diff --git a/core/assets-raw/sprites/blocks/environment/remove-ore.png b/core/assets-raw/sprites/blocks/environment/remove-ore.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b9a0a91bfd8927d88afe6d66332b05cb05ce14f
GIT binary patch
literal 496
zcmV<M0T2F(P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800001b5ch_0Itp)
z=>Px$s!2paR9J=WmOo3wKp4iK6X_-tS|^7rQMwjNql>%X;!=Y6A<Q?(?vQ?nB5qy$
z1a5(XxJjEOAWjyX9U2F@Be{Fu{5iE<@+`f(UU+`@-h1yI&}zpLvJpj*8vOj)tNhSC
zJ}LA+(pS}X)(Zf@Rgz@+Iq~JW0D#k*J2h7AWGUSo0KCU1W6T;LSuG3wzB54I8L;LY
z#6C6Y;^>W>91gDqHD3ooTs4aUI6HhNjTON2eU=AFR!bunn#$M$&RhqPFikb}{6Z8J
zfU{oIGS@-$TmXQ%>sD@+0bnaC5@~N_taR%6LLUQibs+OStB^{~CZPa|^?*|l1giaS
zb)c@uV`!r~E2p&c2($nSpvl?QMIEoY`gIb{CZRw{0^Y_C>UdM23Lx!`Fb720O=cK_
z&;Gn|evB=}3ZlkVNOMk<IrpEj1u_zVd9Uln)auL3DUmtwaxnh+9gYQHKEF6MH3yhu
z*zIfzed@;3S8rp^0&G((02KupiN9KXDF7#GJDNy;SC#_kQQ%a!5>k)at>pmsqOJt!
mb~c4ba8co&TxI610N@+s1FR)(a@=$P0000<MNUMnLSTZvN!HN-

literal 0
HcmV?d00001

diff --git a/core/assets-raw/sprites/blocks/environment/remove-wall.png b/core/assets-raw/sprites/blocks/environment/remove-wall.png
new file mode 100644
index 0000000000000000000000000000000000000000..be52153748bff7167802bc7940623ccbb8036526
GIT binary patch
literal 253
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}+dW+zLn2z=
zhDY-?7;rf2KMl266#g;f^Z6e)Vz{HFCVy0Kyu?v5`=-H0U(58U=_PDA)n;s4rLLVW
z^zE4Bupv)=CyPF(NOm5hL}x4Kf&)H<#szF~o~>mSMY9<zJH^?%8GHV0J%2Lo_ert!
zLK%*Ww(Yx+SM8~Ai?N$~Z`^_c(_nM|LgS#0svGuE3bIZwrMy}_w2odn>`^7--c@a=
zq1be-YmfCI7KsF{Z!agroi6U&E3bC=Ng0E8_7sKP>%$p=USRNa^>bP0l+XkK@yuRc

literal 0
HcmV?d00001

diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties
index 08f271d82d..d39528c634 100644
--- a/core/assets/bundles/bundle.properties
+++ b/core/assets/bundles/bundle.properties
@@ -1563,6 +1563,8 @@ block.graphite-press.name = Graphite Press
 block.multi-press.name = Multi-Press
 block.constructing = {0} [lightgray](Constructing)
 block.spawn.name = Enemy Spawn
+block.remove-wall.name = Remove Wall
+block.remove-ore.name = Remove Ore
 block.core-shard.name = Core: Shard
 block.core-foundation.name = Core: Foundation
 block.core-nucleus.name = Core: Nucleus
diff --git a/core/assets/icons/icons.properties b/core/assets/icons/icons.properties
index 64963dd203..e04db3aa06 100755
--- a/core/assets/icons/icons.properties
+++ b/core/assets/icons/icons.properties
@@ -588,3 +588,5 @@
 63094=cat|cat
 63093=world-switch|block-world-switch-ui
 63092=dynamic|status-dynamic-ui
+63091=remove-wall|block-remove-wall-ui
+63090=remove-ore|block-remove-ore-ui
diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java
index 66947331b2..6fac7f0e9b 100644
--- a/core/src/mindustry/content/Blocks.java
+++ b/core/src/mindustry/content/Blocks.java
@@ -43,7 +43,7 @@ public class Blocks{
     public static Block
 
     //environment
-    air, spawn, cliff, deepwater, water, taintedWater, deepTaintedWater, tar, slag, cryofluid, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space, empty,
+    air, spawn, removeWall, removeOre, cliff, deepwater, water, taintedWater, deepTaintedWater, tar, slag, cryofluid, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space, empty,
     dacite, rhyolite, rhyoliteCrater, roughRhyolite, regolith, yellowStone, redIce, redStone, denseRedStone,
     arkyciteFloor, arkyicStone,
     redmat, bluemat,
@@ -174,6 +174,10 @@ public class Blocks{
 
         spawn = new SpawnBlock("spawn");
 
+        removeWall = new RemoveWall("remove-wall");
+
+        removeOre = new RemoveOre("remove-ore");
+
         cliff = new Cliff("cliff"){{
             inEditor = false;
             saveData = true;
diff --git a/core/src/mindustry/editor/MapEditorDialog.java b/core/src/mindustry/editor/MapEditorDialog.java
index c1f3a6e888..29a18cb200 100644
--- a/core/src/mindustry/editor/MapEditorDialog.java
+++ b/core/src/mindustry/editor/MapEditorDialog.java
@@ -715,7 +715,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
 
     private void addBlockSelection(Table cont){
         blockSelection = new Table();
-        pane = new ScrollPane(blockSelection);
+        pane = new ScrollPane(blockSelection, Styles.smallPane);
         pane.setFadeScrollBars(false);
         pane.setOverscroll(true, false);
         pane.exited(() -> {
@@ -732,7 +732,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
         cont.row();
         cont.table(Tex.underline, extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
         cont.row();
-        cont.add(pane).expandY().top().left();
+        cont.add(pane).expandY().growX().top().left();
 
         rebuildBlockSelection("");
     }
@@ -762,7 +762,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
                     || (!searchText.isEmpty() && !block.localizedName.toLowerCase().contains(searchText.toLowerCase()))
             ) continue;
 
-            ImageButton button = new ImageButton(Tex.whiteui, Styles.squareTogglei);
+            ImageButton button = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei);
             button.getStyle().imageUp = new TextureRegionDrawable(region);
             button.clicked(() -> editor.drawBlock = block);
             button.resizeImage(8 * 4f);
@@ -771,7 +771,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
 
             if(i == 0) editor.drawBlock = block;
 
-            if(++i % 4 == 0){
+            if(++i % 6 == 0){
                 blockSelection.row();
             }
         }
diff --git a/core/src/mindustry/input/MobileInput.java b/core/src/mindustry/input/MobileInput.java
index ce16f8d915..eff27140cb 100644
--- a/core/src/mindustry/input/MobileInput.java
+++ b/core/src/mindustry/input/MobileInput.java
@@ -265,11 +265,11 @@ public class MobileInput extends InputHandler implements GestureListener{
         }).name("confirmplace");
     }
 
-    boolean showCancel(){
-        return !player.dead() && (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchem();
+    public boolean showCancel(){
+        return !player.dead() && (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchematic();
     }
 
-    boolean hasSchem(){
+    public boolean hasSchematic(){
         return lastSchematic != null && !selectPlans.isEmpty();
     }
 
@@ -290,7 +290,7 @@ public class MobileInput extends InputHandler implements GestureListener{
         });
 
         group.fill(t -> {
-            t.visible(() -> !showCancel() && block == null && !hasSchem());
+            t.visible(() -> !showCancel() && block == null && !hasSchematic() && !state.rules.editor);
             t.bottom().left();
 
             t.button("@command.queue", Icon.rightOpen, Styles.clearTogglet, () -> {
@@ -310,7 +310,7 @@ public class MobileInput extends InputHandler implements GestureListener{
         });
 
         group.fill(t -> {
-            t.visible(this::hasSchem);
+            t.visible(this::hasSchematic);
             t.bottom().left();
             t.table(Tex.pane, b -> {
                 b.defaults().size(50f);
@@ -759,7 +759,7 @@ public class MobileInput extends InputHandler implements GestureListener{
             payloadTarget = null;
         }
 
-        if(locked || block != null || scene.hasField() || hasSchem() || selectPlans.size > 0){
+        if(locked || block != null || scene.hasField() || hasSchematic() || selectPlans.size > 0){
             commandMode = false;
         }
 
diff --git a/core/src/mindustry/net/CrashHandler.java b/core/src/mindustry/net/CrashHandler.java
index b559d654e8..cb60f4ef33 100644
--- a/core/src/mindustry/net/CrashHandler.java
+++ b/core/src/mindustry/net/CrashHandler.java
@@ -64,7 +64,7 @@ public class CrashHandler{
 
             //don't create crash logs for custom builds, as it's expected
             if(OS.username.equals("anuke") && !"steam".equals(Version.modifier)){
-            //    System.exit(1);
+                System.exit(1);
             }
 
             //attempt to load version regardless
diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java
index cf7a2866b5..c676f67bb1 100644
--- a/core/src/mindustry/ui/fragments/HudFragment.java
+++ b/core/src/mindustry/ui/fragments/HudFragment.java
@@ -14,6 +14,7 @@ import arc.scene.ui.ImageButton.*;
 import arc.scene.ui.layout.*;
 import arc.struct.*;
 import arc.util.*;
+import mindustry.*;
 import mindustry.annotations.Annotations.*;
 import mindustry.content.*;
 import mindustry.core.GameState.*;
@@ -27,8 +28,11 @@ import mindustry.input.*;
 import mindustry.net.Packets.*;
 import mindustry.type.*;
 import mindustry.ui.*;
+import mindustry.world.*;
+import mindustry.world.blocks.environment.*;
 import mindustry.world.blocks.storage.*;
 import mindustry.world.blocks.storage.CoreBlock.*;
+import mindustry.world.meta.*;
 
 import static mindustry.Vars.*;
 import static mindustry.gen.Tex.*;
@@ -49,6 +53,80 @@ public class HudFragment{
     private Table lastUnlockLayout;
     private long lastToast;
 
+    private Seq<Block> blocksOut = new Seq<>();
+
+    private void addBlockSelection(Table cont){
+        Table blockSelection = new Table();
+        var pane = new ScrollPane(blockSelection, Styles.smallPane);
+        pane.setFadeScrollBars(false);
+        Planet[] last = {state.rules.planet};
+        pane.update(() -> {
+            if(pane.hasScroll()){
+                Element result = Core.scene.getHoverElement();
+                if(result == null || !result.isDescendantOf(pane)){
+                    Core.scene.setScrollFocus(null);
+                }
+            }
+
+            if(state.rules.planet != last[0]){
+                last[0] = state.rules.planet;
+                rebuildBlockSelection(blockSelection, "");
+            }
+        });
+
+        cont.table(search -> {
+            search.image(Icon.zoom).padRight(8);
+            search.field("", text -> rebuildBlockSelection(blockSelection, text)).growX()
+            .name("editor/search").maxTextLength(maxNameLength).get().setMessageText("@players.search");
+        }).growX().pad(-2).padLeft(6f);
+        cont.row();
+        cont.add(pane).expandY().top().left();
+
+        rebuildBlockSelection(blockSelection, "");
+    }
+
+    private void rebuildBlockSelection(Table blockSelection, String searchText){
+        blockSelection.clear();
+
+        blocksOut.clear();
+        blocksOut.addAll(Vars.content.blocks());
+        blocksOut.sort((b1, b2) -> {
+            int synth = Boolean.compare(b1.synthetic(), b2.synthetic());
+            if(synth != 0) return synth;
+            int ore = Boolean.compare(b1 instanceof OverlayFloor && b1 != Blocks.removeOre, b2 instanceof OverlayFloor && b2 != Blocks.removeOre);
+            if(ore != 0) return ore;
+            return Integer.compare(b1.id, b2.id);
+        });
+
+        int i = 0;
+
+        for(Block block : blocksOut){
+            TextureRegion region = block.uiIcon;
+
+            if(!Core.atlas.isFound(region)
+            || (!block.inEditor && !(block instanceof RemoveWall) && !(block instanceof RemoveOre))
+            || !block.isOnPlanet(state.rules.planet)
+            || block.buildVisibility == BuildVisibility.debugOnly
+            || (!searchText.isEmpty() && !block.localizedName.toLowerCase().contains(searchText.toLowerCase()))
+            ) continue;
+
+            ImageButton button = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei);
+            button.getStyle().imageUp = new TextureRegionDrawable(region);
+            button.clicked(() -> control.input.block = block);
+            button.resizeImage(8 * 4f);
+            button.update(() -> button.setChecked(control.input.block == block));
+            blockSelection.add(button).size(48f).tooltip(block.localizedName);
+
+            if(++i % 6 == 0){
+                blockSelection.row();
+            }
+        }
+
+        if(i == 0){
+            blockSelection.add("@none.found").padLeft(54f).padTop(10f);
+        }
+    }
+
     public void build(Group parent){
 
         //warn about guardian/boss waves
@@ -247,26 +325,38 @@ public class HudFragment{
 
             editorMain.name = "editor";
             editorMain.table(Tex.buttonEdge4, t -> {
-                //t.margin(0f);
                 t.name = "teams";
-                t.add("@editor.teams").growX().left();
-                t.row();
-                t.table(teams -> {
+                t.top().table(teams -> {
                     teams.left();
                     int i = 0;
                     for(Team team : Team.baseTeams){
-                        ImageButton button = teams.button(Tex.whiteui, Styles.clearNoneTogglei, 40f, () -> Call.setPlayerTeamEditor(player, team))
+                        ImageButton button = teams.button(Tex.whiteui, Styles.clearNoneTogglei, 38f, () -> Call.setPlayerTeamEditor(player, team))
                         .size(50f).margin(6f).get();
                         button.getImageCell().grow();
                         button.getStyle().imageUpColor = team.color;
                         button.update(() -> button.setChecked(player.team() == team));
 
-                        if(++i % 3 == 0){
+                        if(++i % 6 == 0){
                             teams.row();
                         }
                     }
-                }).left();
+                }).top().left();
+
+                t.row();
+
+                t.table(blocks -> {
+                    addBlockSelection(blocks);
+                }).fillX().left();
             }).width(dsize * 5 + 4f);
+            if(mobile){
+                editorMain.row().spacerY(() -> {
+                    if(control.input instanceof MobileInput mob){
+                        if(mob.hasSchematic()) return 156f;
+                        if(mob.showCancel()) return 50f;
+                    }
+                    return 0f;
+                });
+            }
             editorMain.visible(() -> shown && state.isEditor());
 
             //fps display
diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java
index 7b83ab0ae2..eaafecee8c 100644
--- a/core/src/mindustry/world/Block.java
+++ b/core/src/mindustry/world/Block.java
@@ -920,6 +920,11 @@ public class Block extends UnlockableContent implements Senseable{
         placeBegan(tile, previous);
     }
 
+    /** Called when building of this block ends. */
+    public void placeEnded(Tile tile, @Nullable Unit builder){
+
+    }
+
     /** Called right before building of this block begins. */
     public void beforePlaceBegan(Tile tile, Block previous){
 
diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java
index 6288a0d235..5388ea9789 100644
--- a/core/src/mindustry/world/Build.java
+++ b/core/src/mindustry/world/Build.java
@@ -199,6 +199,10 @@ public class Build{
 
         if(tile == null) return false;
 
+        if(!type.canPlaceOn(tile, team, rotation)){
+            return false;
+        }
+
         //floors have different checks
         if(type.isFloor()){
             return type.isOverlay() ? tile.overlay() != type : tile.floor() != type;
@@ -213,10 +217,6 @@ public class Build{
             return false;
         }
 
-        if(!type.canPlaceOn(tile, team, rotation)){
-            return false;
-        }
-
         int offsetx = -(type.size - 1) / 2;
         int offsety = -(type.size - 1) / 2;
 
diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java
index a68424552b..0a49f8212f 100644
--- a/core/src/mindustry/world/blocks/ConstructBlock.java
+++ b/core/src/mindustry/world/blocks/ConstructBlock.java
@@ -110,6 +110,8 @@ public class ConstructBlock extends Block{
             if(shouldPlay()) block.placeSound.at(tile, block.placePitchChange ? calcPitch(true) : 1f);
         }
 
+        block.placeEnded(tile, builder);
+
         Events.fire(new BlockBuildEndEvent(tile, builder, team, false, config));
     }
 
diff --git a/core/src/mindustry/world/blocks/environment/RemoveOre.java b/core/src/mindustry/world/blocks/environment/RemoveOre.java
new file mode 100644
index 0000000000..b50d019c4f
--- /dev/null
+++ b/core/src/mindustry/world/blocks/environment/RemoveOre.java
@@ -0,0 +1,51 @@
+package mindustry.world.blocks.environment;
+
+import arc.graphics.g2d.*;
+import arc.util.*;
+import mindustry.content.*;
+import mindustry.entities.units.*;
+import mindustry.game.*;
+import mindustry.gen.*;
+import mindustry.world.*;
+
+public class RemoveOre extends OverlayFloor{
+
+    public RemoveOre(String name){
+        super(name);
+
+        allowRectanglePlacement = true;
+        placeEffect = Fx.rotateBlock;
+        instantBuild = true;
+        ignoreBuildDarkness = true;
+        placeableLiquid = true;
+        inEditor = false;
+        variants = 0;
+    }
+
+    @Override
+    public void drawPlan(BuildPlan plan, Eachable<BuildPlan> list, boolean valid, float alpha){
+        Draw.reset();
+        Draw.alpha(alpha * (valid ? 1f : 0.2f));
+        float prevScale = Draw.scl;
+        Draw.scl *= plan.animScale;
+        drawPlanRegion(plan, list);
+        Draw.scl = prevScale;
+        Draw.reset();
+    }
+
+    @Override
+    public boolean canPlaceOn(Tile tile, Team team, int rotation){
+        return tile.overlay() != Blocks.air;
+    }
+
+    @Override
+    public boolean canReplace(Block other){
+        return true;
+    }
+
+    @Override
+    public void placeEnded(Tile tile, @Nullable Unit builder){
+        tile.setOverlay(Blocks.air);
+    }
+
+}
diff --git a/core/src/mindustry/world/blocks/environment/RemoveWall.java b/core/src/mindustry/world/blocks/environment/RemoveWall.java
new file mode 100644
index 0000000000..039997acdc
--- /dev/null
+++ b/core/src/mindustry/world/blocks/environment/RemoveWall.java
@@ -0,0 +1,50 @@
+package mindustry.world.blocks.environment;
+
+import arc.graphics.g2d.*;
+import arc.util.*;
+import mindustry.content.*;
+import mindustry.entities.units.*;
+import mindustry.game.*;
+import mindustry.gen.*;
+import mindustry.world.*;
+
+public class RemoveWall extends Block{
+
+    public RemoveWall(String name){
+        super(name);
+
+        allowRectanglePlacement = true;
+        placeEffect = Fx.rotateBlock;
+        instantBuild = true;
+        ignoreBuildDarkness = true;
+        placeableLiquid = true;
+        inEditor = false;
+    }
+
+    @Override
+    public void drawPlan(BuildPlan plan, Eachable<BuildPlan> list, boolean valid, float alpha){
+        Draw.reset();
+        Draw.alpha(alpha * (valid ? 1f : 0.2f));
+        float prevScale = Draw.scl;
+        Draw.scl *= plan.animScale;
+        drawPlanRegion(plan, list);
+        Draw.scl = prevScale;
+        Draw.reset();
+    }
+
+    @Override
+    public boolean canPlaceOn(Tile tile, Team team, int rotation){
+        return tile.block() != Blocks.air;
+    }
+
+    @Override
+    public boolean canReplace(Block other){
+        return other != Blocks.air && !other.synthetic();
+    }
+
+    @Override
+    public void placeEnded(Tile tile, @Nullable Unit builder){
+        tile.setBlock(Blocks.air);
+    }
+
+}
diff --git a/gradle.properties b/gradle.properties
index 10164c81f1..3f9f316bab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -26,4 +26,4 @@ org.gradle.caching=true
 org.gradle.internal.http.socketTimeout=100000
 org.gradle.internal.http.connectionTimeout=100000
 android.enableR8.fullMode=false
-archash=66ae776c9f
+archash=bf2ab4d045