map editor windows - edit tools

This commit is contained in:
sharlottes
2022-04-19 19:45:00 +09:00
parent de799fffd7
commit 8cc932a647
6 changed files with 624 additions and 2 deletions

View File

@@ -0,0 +1,268 @@
package UnitInfo.ui;
import UnitInfo.ui.windows.WindowTables;
import arc.func.Boolf;
import arc.func.Cons;
import arc.input.KeyCode;
import arc.math.Mathf;
import arc.math.geom.Bresenham2;
import arc.math.geom.Point2;
import arc.struct.IntSeq;
import arc.util.Structs;
import mindustry.content.Blocks;
import mindustry.game.Team;
import mindustry.world.Block;
import mindustry.world.Tile;
import static UnitInfo.ui.windows.MapEditorDisplay.drawTeam;
import static UnitInfo.ui.windows.MapEditorDisplay.selected;
import static UnitInfo.ui.windows.WindowTables.editorTable;
import static mindustry.Vars.world;
public enum EditorTool{
zoom(KeyCode.v),
pick(KeyCode.i){
public void touched(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height())) return;
Tile tile = world.tile(x, y);
selected = tile.block() == Blocks.air || !tile.block().inEditor ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block();
}
},
line(KeyCode.l, "replace", "orthogonal"){
@Override
public void touchedLine(int x1, int y1, int x2, int y2){
//straight
if(mode == 1){
if(Math.abs(x2 - x1) > Math.abs(y2 - y1)){
y2 = y1;
}else{
x2 = x1;
}
}
Bresenham2.line(x1, y1, x2, y2, (x, y) -> {
if(mode == 0){
//replace
editorTable.drawBlocksReplace(x, y);
}else{
//normal
editorTable.drawBlocks(x, y);
}
});
}
},
pencil(KeyCode.b, "replace", "square", "drawteams"){
{
edit = true;
draggable = true;
}
@Override
public void touched(int x, int y){
if(mode == -1){
//normal mode
editorTable.drawBlocks(x, y);
}else if(mode == 0){
//replace mode
editorTable.drawBlocksReplace(x, y);
}else if(mode == 1){
//square mode
editorTable.drawBlocks(x, y, true, tile -> true);
}else if(mode == 2){
//draw teams
editorTable.drawCircle(x, y, tile -> tile.setTeam(drawTeam));
}
}
},
eraser(KeyCode.e, "eraseores"){
{
edit = true;
draggable = true;
}
@Override
public void touched(int x, int y){
editorTable.drawCircle(x, y, tile -> {
if(mode == -1){
//erase block
tile.remove();
}else if(mode == 0){
//erase ore
tile.clearOverlay();
}
});
}
},
fill(KeyCode.g, "replaceall", "fillteams"){
{
edit = true;
}
IntSeq stack = new IntSeq();
@Override
public void touched(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || world.tile(x, y).block()!=null&&world.tile(x, y).block().isMultiblock()) return;
Tile tile = world.tile(x, y);
//mode 0 or 1, fill everything with the floor/tile or replace it
if(mode == 0 || mode == -1){
if(tile.block().isMultiblock()) return;
Boolf<Tile> tester;
Cons<Tile> setter;
Block drawBlock = selected;
if(drawBlock.isOverlay()){
Block dest = tile.overlay();
if(dest == drawBlock) return;
tester = t -> t.overlay() == dest && (t.floor().hasSurface() || !t.floor().needsSurface);
setter = t -> t.setOverlay(drawBlock);
}else if(drawBlock.isFloor()){
Block dest = tile.floor();
if(dest == drawBlock) return;
tester = t -> t.floor() == dest;
setter = t -> t.setFloorUnder(drawBlock.asFloor());
}else{
Block dest = tile.block();
if(dest == drawBlock) return;
tester = t -> t.block() == dest;
setter = t -> t.setBlock(drawBlock, drawTeam);
}
//replace only when the mode is 0 using the specified functions
fill(x, y, mode == 0, tester, setter);
}else if(mode == 1){ //mode 1 is team fill
//only fill synthetic blocks, it's meaningless otherwise
if(tile.synthetic()){
Team dest = tile.team();
if(dest == drawTeam) return;
fill(x, y, false, t -> t.getTeamID() == dest.id && t.synthetic(), t -> t.setTeam(drawTeam));
}
}
}
void fill(int x, int y, boolean replace, Boolf<Tile> tester, Cons<Tile> filler){
int width = world.width(), height = world.height();
if(replace){
//just do it on everything
for(int cx = 0; cx < width; cx++){
for(int cy = 0; cy < height; cy++){
Tile tile = world.tile(cx, cy);
if(tester.get(tile)){
filler.get(tile);
}
}
}
}else{
//perform flood fill
int x1;
stack.clear();
stack.add(Point2.pack(x, y));
try{
while(stack.size > 0 && stack.size < width*height){
int popped = stack.pop();
x = Point2.x(popped);
y = Point2.y(popped);
x1 = x;
while(x1 >= 0 && tester.get(world.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.get(world.tile(x1, y))){
filler.get(world.tile(x1, y));
if(!spanAbove && y > 0 && tester.get(world.tile(x1, y - 1))){
stack.add(Point2.pack(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.get(world.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.get(world.tile(x1, y + 1))){
stack.add(Point2.pack(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.get(world.tile(x1, y + 1))){
spanBelow = false;
}
x1++;
}
}
stack.clear();
}catch(OutOfMemoryError e){
//hack
stack = null;
System.gc();
e.printStackTrace();
stack = new IntSeq();
}
}
}
},
spray(KeyCode.r, "replace"){
final double chance = 0.012;
{
edit = true;
draggable = true;
}
@Override
public void touched(int x, int y){
//floor spray
if(selected.isFloor()){
editorTable.drawCircle(x, y, tile -> {
if(Mathf.chance(chance)){
tile.setFloor(selected.asFloor());
}
});
}else if(mode == 0){ //replace-only mode, doesn't affect air
editorTable.drawBlocks(x, y, tile -> Mathf.chance(chance) && tile.block() != Blocks.air);
}else{
editorTable.drawBlocks(x, y, tile -> Mathf.chance(chance));
}
}
};
public static final EditorTool[] all = values();
/** All the internal alternate placement modes of this tool. */
public final String[] altModes;
/** Key to activate this tool. */
public KeyCode key = KeyCode.unset;
/** The current alternate placement mode. -1 is the standard mode, no changes.*/
public int mode = -1;
/** Whether this tool causes canvas changes when touched.*/
public boolean edit;
/** Whether this tool should be dragged across the canvas when the mouse moves.*/
public boolean draggable;
EditorTool(){
this(new String[]{});
}
EditorTool(KeyCode code){
this(new String[]{});
this.key = code;
}
EditorTool(String... altModes){
this.altModes = altModes;
}
EditorTool(KeyCode code, String... altModes){
this.altModes = altModes;
this.key = code;
}
public void touched(int x, int y){}
public void touchedLine(int x1, int y1, int x2, int y2){}
}

View File

@@ -19,7 +19,8 @@ public class HUDFragment extends Fragment{
waveTable,
coreTable,
playerTable,
toolTable
toolTable,
editorTable
)).visible(TaskbarTable.visibility);
// windows (totally not a copyright violation)
@@ -28,6 +29,7 @@ public class HUDFragment extends Fragment{
t.add(coreTable).size(250f).visible(false);
t.add(playerTable).size(250f).visible(false);
t.add(toolTable).size(250f).visible(false);
t.add(editorTable).size(250f).visible(false);
t.update(()->{
for (Element child : t.getChildren()) {

View File

@@ -1,13 +1,26 @@
package UnitInfo.ui.draws;
import arc.Events;
import arc.func.Boolf;
import arc.func.Cons;
import arc.input.KeyCode;
import arc.math.Angles;
import arc.math.geom.Geometry;
import arc.math.geom.Point2;
import arc.scene.style.TextureRegionDrawable;
import arc.scene.ui.Button;
import arc.scene.ui.CheckBox;
import arc.scene.ui.layout.Table;
import arc.struct.IntSeq;
import arc.util.Structs;
import mindustry.content.Blocks;
import mindustry.entities.Units;
import mindustry.game.EventType;
import mindustry.game.Team;
import mindustry.gen.*;
import mindustry.logic.Ranged;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.blocks.ControlBlock;
import mindustry.world.blocks.defense.turrets.Turret;
@@ -20,6 +33,7 @@ public class UtilDraw extends OverDraw {
UtilDraw(String name, TextureRegionDrawable icon) {
super(name, icon);
registerOption("autoShooting");
}
@Override
@@ -77,4 +91,5 @@ public class UtilDraw extends OverDraw {
unit.controlWeapons(player.shooting && !boosted);
}
}
}

View File

@@ -0,0 +1,335 @@
package UnitInfo.ui.windows;
import UnitInfo.ui.EditorTool;
import UnitInfo.ui.OverScrollPane;
import UnitInfo.ui.Updatable;
import arc.Core;
import arc.Events;
import arc.func.Boolf;
import arc.func.Cons;
import arc.func.Prov;
import arc.graphics.Color;
import arc.input.KeyCode;
import arc.math.Mathf;
import arc.math.geom.Point2;
import arc.math.geom.Vec2;
import arc.scene.event.Touchable;
import arc.scene.style.TextureRegionDrawable;
import arc.scene.ui.*;
import arc.scene.ui.layout.Scl;
import arc.scene.ui.layout.Table;
import arc.scene.utils.Elem;
import arc.struct.IntSeq;
import arc.struct.Seq;
import arc.util.*;
import mindustry.Vars;
import mindustry.content.Blocks;
import mindustry.editor.DrawOperation;
import mindustry.game.EventType;
import mindustry.game.Team;
import mindustry.gen.Icon;
import mindustry.gen.Tex;
import mindustry.gen.TileOp;
import mindustry.graphics.Pal;
import mindustry.ui.Styles;
import mindustry.world.Block;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class MapEditorDisplay extends WindowTable implements Updatable {
Vec2 scrollPos = new Vec2(0, 0);
TextField search;
Table window;
float heat;
float brushSize = -1;
UnitInfo.ui.EditorTool tool;
public static Team drawTeam = Team.sharded;
public static Block selected = Blocks.router;
public MapEditorDisplay() {
super("Map Editor Display", Icon.map, t -> {});
}
@Override
public void build() {
scrollPos = new Vec2(0, 0);
search = Elem.newField(null, f->{});
search.setMessageText("Search...");
top();
topBar();
table(Styles.black8, table -> {
window=table;
table.table(t->{
t.left().background(Tex.underline2);
t.label(()->"[accent]"+selected.localizedName+"[] "+selected.emoji());
t.add(search).growX().pad(8).name("search");
}).growX().row();
table.add(new OverScrollPane(rebuild(), Styles.nonePane, scrollPos).disableScroll(true, false)).grow().name("editor-pane");
}).top().right().grow().get().parent = null;
resizeButton();
}
boolean hold = false;
int pastX, pastY;
@Override
public void update() {
heat += Time.delta;
if(heat >= 60f) {
heat = 0f;
resetPane();
}
if(tool != null) {
if(Core.input.isTouched()) {
if(!(!mobile&&Core.input.keyDown(KeyCode.mouseLeft))) return;
if(tool== EditorTool.line) {
if(!hold) {
pastX = (int) player.mouseX / 8;
pastY = (int) player.mouseY / 8;
}
hold = true;
}
else {
pastX = (int) player.mouseX / 8;
pastY = (int) player.mouseY / 8;
}
tool.touched(pastX, pastY);
}
else if(tool== EditorTool.line) {
if(hold&&pastX>=0&&pastY>=0) tool.touchedLine(pastX, pastY, (int) player.mouseX/8, (int) player.mouseY/8);
hold = false;
pastX = -1;
pastY = -1;
}
}
}
void resetPane() {
ScrollPane pane = find("editor-pane");
pane.setWidget(rebuild());
}
public Table rebuild() {
return new Table(table-> {
table.top();
Seq<Block> blocks = Vars.content.blocks().copy();
if(search.getText().length() > 0){
blocks.filter(p -> p.name.toLowerCase().contains(search.getText().toLowerCase())||p.localizedName.toLowerCase().contains(search.getText().toLowerCase()));
}
table.table(select->buildTable(Blocks.boulder, select, blocks, ()->selected, block->selected=block, false)).marginTop(16f).marginBottom(16f).row();
table.image().height(4f).color(Pal.gray).growX().row();
table.table(body-> {
body.table(tools -> {
tools.top().left();
tools.table(title -> title.left().background(Tex.underline2).add("Tools [accent]"+(tool==null?"":tool.name())+"[]")).growX().row();
tools.table(bt->{
Cons<UnitInfo.ui.EditorTool> addTool = tool -> {
ImageButton button = new ImageButton(ui.getIcon(tool.name()), Styles.clearTogglei);
button.clicked(() -> {
button.toggle();
if(this.tool==tool) this.tool = null;
else this.tool = tool;
resetPane();
});
button.update(()->button.setChecked(this.tool == tool));
Label mode = new Label("");
mode.setColor(Pal.remove);
mode.update(() -> mode.setText(tool.mode == -1 ? "" : "M" + (tool.mode + 1) + " "));
mode.setAlignment(Align.bottomRight, Align.bottomRight);
mode.touchable = Touchable.disabled;
bt.stack(button, mode);
};
addTool.get(UnitInfo.ui.EditorTool.line);
addTool.get(UnitInfo.ui.EditorTool.pencil);
addTool.get(UnitInfo.ui.EditorTool.eraser);
addTool.get(UnitInfo.ui.EditorTool.fill);
addTool.get(UnitInfo.ui.EditorTool.spray);
});
tools.row();
Slider slider = new Slider(1, 16, 1, false);
slider.moved(size->brushSize=size);
slider.setValue(brushSize);
Label label = new Label("Brush: "+brushSize);
label.touchable = Touchable.disabled;
tools.stack(slider, label).width(window.getWidth()/5).center();
}).left().width(window.getWidth() / 2).margin(8f).growY();
body.image().width(4f).height(body.getHeight()).color(Pal.gray).growY();
body.table(options -> {
options.top().left();
options.table(title -> title.left().background(Tex.underline2).add("Options [accent]"+(tool!=null&&tool.mode>=0&&tool.mode<tool.altModes.length?tool.altModes[tool.mode]:"")+"[]")).growX().row();
options.table(option-> {
option.defaults().size(300f, 70f).left();
if(tool==null) return;
for (int i = 0; i < tool.altModes.length; i++) {
int mode = i;
String name = tool.altModes[i];
option.button(b -> {
b.left().marginLeft(6);
b.setStyle(Styles.clearTogglet);
b.add(Core.bundle.get("toolmode." + name)).left().row();
b.add(Core.bundle.get("toolmode." + name + ".description")).color(Color.lightGray).left();
}, () -> {
tool.mode = (tool.mode == mode ? -1 : mode);
}).update(b -> b.setChecked(tool.mode == mode));
option.row();
}
});
}).left().width(window.getWidth() / 2).margin(8f).growY();
}).grow();
});
}
<T extends Block> void buildTable(@Nullable Block block, Table table, Seq<T> items, Prov<T> holder, Cons<T> consumer, boolean closeSelect){
ButtonGroup<ImageButton> group = new ButtonGroup<>();
group.setMinCheckCount(0);
Table cont = new Table();
cont.defaults().size(40);
int i = 0;
int max = Math.max(4, Math.round(window.getWidth()/64));
for(T item : items){
if(!item.unlockedNow()) continue;
ImageButton button = cont.button(Tex.whiteui, Styles.clearToggleTransi, 24, () -> {
if(closeSelect) control.input.frag.config.hideConfig();
}).group(group).tooltip(t->t.background(Styles.black8).add(item.localizedName)).get();
button.changed(() -> consumer.get(button.isChecked() ? item : null));
button.getStyle().imageUp = new TextureRegionDrawable(item.uiIcon);
button.update(() -> button.setChecked(holder.get() == item));
if(i++ % max == max-1){
cont.row();
}
}
//add extra blank spaces so it looks nice
if(i % max != 0){
int remaining = max - (i % max);
for(int j = 0; j < remaining; j++){
cont.image(Styles.black6);
}
}
ScrollPane pane = new ScrollPane(cont, Styles.smallPane);
pane.setScrollingDisabled(true, false);
if(block != null){
pane.setScrollYForce(block.selectScroll);
pane.update(() -> {
block.selectScroll = pane.getScrollY();
});
}
pane.setOverscroll(false, false);
table.add(pane).maxHeight(Scl.scl(40 * 5));
}
public void drawBlocksReplace(int x, int y){
drawBlocks(x, y, tile -> tile.block() != Blocks.air || selected.isFloor());
}
public void drawBlocks(int x, int y){
drawBlocks(x, y, false, tile -> true);
}
public void drawBlocks(int x, int y, Boolf<Tile> tester){
drawBlocks(x, y, false, tester);
}
int rotation = 0;
public void drawBlocks(int x, int y, boolean square, Boolf<Tile> tester){
if(selected.isMultiblock()){
x = Mathf.clamp(x, (selected.size - 1) / 2, world.width() - selected.size / 2 - 1);
y = Mathf.clamp(y, (selected.size - 1) / 2, world.height() - selected.size / 2 - 1);
if(!hasOverlap(x, y)){
world.tile(x, y).setBlock(selected, drawTeam, rotation);
}
}else{
boolean isFloor = selected.isFloor() && selected != Blocks.air;
Cons<Tile> drawer = tile -> {
if(!tester.get(tile)) return;
if(isFloor){
tile.setFloor(selected.asFloor());
}else if(!(tile.block().isMultiblock() && !selected.isMultiblock())){
tile.setBlock(selected, drawTeam, rotation);
}
};
if(square){
drawSquare(x, y, drawer);
}else{
drawCircle(x, y, drawer);
}
}
}
public void drawCircle(int x, int y, Cons<Tile> drawer){
int clamped = (int)brushSize;
for(int rx = -clamped; rx <= clamped; rx++){
for(int ry = -clamped; ry <= clamped; ry++){
if(Mathf.within(rx, ry, brushSize - 0.5f + 0.0001f)){
int wx = x + rx, wy = y + ry;
if(wx < 0 || wy < 0 || wx >= world.width() || wy >= world.height()){
continue;
}
drawer.get(world.tile(wx, wy));
}
}
}
}
public void drawSquare(int x, int y, Cons<Tile> drawer){
int clamped = (int)brushSize;
for(int rx = -clamped; rx <= clamped; rx++){
for(int ry = -clamped; ry <= clamped; ry++){
int wx = x + rx, wy = y + ry;
if(wx < 0 || wy < 0 || wx >= world.width() || wy >= world.height()){
continue;
}
drawer.get(world.tile(wx, wy));
}
}
}
boolean hasOverlap(int x, int y){
Tile tile = world.tile(x, y);
//allow direct replacement of blocks of the same size
if(tile != null && tile.isCenter() && tile.block() != selected && tile.block().size == selected.size && tile.x == x && tile.y == y){
return false;
}
//else, check for overlap
int offsetx = -(selected.size - 1) / 2;
int offsety = -(selected.size - 1) / 2;
for(int dx = 0; dx < selected.size; dx++){
for(int dy = 0; dy < selected.size; dy++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
Tile other = world.tile(worldx, worldy);
if(other != null && other.block().isMultiblock()){
return true;
}
}
}
return false;
}
}

View File

@@ -23,9 +23,9 @@ import static mindustry.Vars.*;
public class PlayerDisplay extends WindowTable implements Updatable {
Vec2 scrollPos = new Vec2(0, 0);
TextField search;
ImageButton.ImageButtonStyle ustyle;
@Nullable Player target;
float heat;
ImageButton.ImageButtonStyle ustyle;
public PlayerDisplay() {
super("Player Display", Icon.players, t -> {});

View File

@@ -3,6 +3,7 @@ package UnitInfo.ui.windows;
public class WindowTables {
public static WindowTable
unitTable, waveTable, coreTable, playerTable, toolTable;
public static MapEditorDisplay editorTable;
public static void init() {
unitTable = new UnitDisplay();
@@ -10,5 +11,6 @@ public class WindowTables {
coreTable = new CoreDisplay();
playerTable = new PlayerDisplay();
toolTable = new ToolDisplay();
editorTable = new MapEditorDisplay();
}
}