Files
Informatis/src/informatis/ui/window/MapEditorWindow.java
2022-06-05 15:58:53 +09:00

469 lines
19 KiB
Java

package informatis.ui.window;
import arc.Events;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.style.*;
import arc.struct.*;
import informatis.core.EditorTool;
import mindustry.editor.*;
import mindustry.game.*;
import mindustry.graphics.*;
import informatis.ui.*;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.scene.utils.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class MapEditorWindow extends Window implements Updatable {
Vec2 scrollPos = new Vec2(0, 0);
TextField search;
EditorTool tool;
final Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0];
float heat;
float brushSize = -1;
boolean drawing;
int lastx, lasty;
float lastw, lasth;
public static Team drawTeam = Team.sharded;
public static Block drawBlock = Blocks.router;
public MapEditorWindow() {
super(Icon.map, "editor");
for(int i = 0; i < MapEditor.brushSizes.length; i++){
float size = MapEditor.brushSizes[i];
float mod = size % 1f;
brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Mathf.dst(x, y, index - mod, index - mod) <= size - 0.5f);
}
Events.run(EventType.Trigger.draw, ()->{
float cx = Core.camera.position.x, cy = Core.camera.position.y;
float scaling = 8;
Draw.z(Layer.max);
if(Core.settings.getBool("grid")){
Lines.stroke(1f);
Draw.color(Pal.accent);
for(int i = (int)(-0.5f*Core.camera.height/8); i < (int)(0.5f*Core.camera.height/8); i++) {
Lines.line(Mathf.floor((cx-0.5f*Core.camera.width)/8)*8+4, Mathf.floor((cy + i*8)/8)*8+4, Mathf.floor((cx+0.5f*Core.camera.width)/8)*8+4,Mathf.floor((cy + i*8)/8)*8+4);
}
for(int i = (int)(-0.5f*Core.camera.width/8); i < (int)(0.5f*Core.camera.width/8); i++) {
Lines.line(Mathf.floor((cx + i*8)/8)*8+4, Mathf.floor((cy+0.5f*Core.camera.height)/8)*8+4, Mathf.floor((cx + i*8)/8)*8+4,Mathf.floor((cy-0.5f*Core.camera.height)/8)*8+4);
}
Draw.reset();
}
Tile tile = world.tileWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());
if(tile == null || tool == null || brushSize < 1) return;
int index = 0;
for(int i = 0; i < MapEditor.brushSizes.length; i++){
if(brushSize == MapEditor.brushSizes[i]){
index = i;
break;
}
}
Lines.stroke(Scl.scl(2f), Pal.accent);
if((!drawBlock.isMultiblock() || tool == EditorTool.eraser) && tool != EditorTool.fill){
if(tool == EditorTool.line && drawing){
Lines.poly(brushPolygons[index], lastx, lasty, scaling);
Lines.poly(brushPolygons[index], tile.x*8, tile.y*8, scaling);
}
if((tool.edit || (tool == EditorTool.line && !drawing)) && (!mobile || drawing)){
if(tool == EditorTool.pencil && tool.mode == 1){
Lines.square(tile.x*8, tile.y*8, scaling * (brushSize + 0.5f));
}else{
Lines.poly(brushPolygons[index], tile.x*8-4, tile.y*8-4, scaling);
}
}
}else{
if((tool.edit || tool == EditorTool.line) && (!mobile || drawing)){
float offset = (drawBlock.size % 2 == 0 ? scaling / 2f : 0f);
Lines.square(
tile.x*8 + scaling / 2f + offset,
tile.y*8 + scaling / 2f + offset,
scaling * drawBlock.size / 2f);
}
}
});
}
@Override
public void build(Table table) {
scrollPos = new Vec2(0, 0);
search = Elem.newField(null, f->{});
search.setMessageText(Core.bundle.get("players.search")+"...");
window = table;
table.left();
table.top().background(Styles.black8);
ObjectMap<Drawable, Element> displays = new ObjectMap<>();
displays.put(Icon.map, new Table(display -> {
display.table(t->{
t.left().background(Tex.underline2);
t.label(()-> drawBlock == null ? "[gray]None[]" : "[accent]" + drawBlock.localizedName + "[] "+ drawBlock.emoji());
t.add(search).growX().pad(8).name("search");
}).growX().row();
display.add(new OverScrollPane(rebuildEditor(), Styles.noBarPane, scrollPos).disableScroll(true, false)).grow().name("editor-pane").row();
}));
displays.put(Icon.settings, new Table(display -> {
display.add(new OverScrollPane(rebuildRule(), Styles.noBarPane, scrollPos).disableScroll(true, false)).grow().name("rule-pane").row();
}));
table.table(buttons -> {
buttons.top().left();
displays.each((icon, display) -> {
buttons.button(icon, Styles.clearTogglei, ()->{
Log.info(table.getChildren().get(table.getChildren().size-1));
if(table.getChildren().size > 1) table.getChildren().get(table.getChildren().size-1).remove();
table.add(display).grow();
}).row();
});
}).growY();
}
@Override
public void update() {
//TODO make it more responsive, time -> width delta detect
heat += Time.delta;
if(heat >= 60f) {
heat = 0f;
if(lastw != window.getWidth() || lasth != window.getHeight()) resetPane();
lastw = width;
lasth = height;
}
Tile tile = world.tileWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());
if(tile == null || tool == null || brushSize < 1 || drawBlock == null || hasMouse()) return;
if(Core.input.isTouched()) {
if((tool == EditorTool.line && drawing) || (!mobile && !Core.input.keyDown(KeyCode.mouseLeft))) return;
drawing = true;
lastx = tile.x;
lasty = tile.y;
tool.touched(lastx, lasty);
}
else {
if(tool == EditorTool.line && drawing) tool.touchedLine(lastx, lasty, tile.x, tile.y);
drawing = false;
lastx = -1;
lasty = -1;
}
}
void resetPane() {
ScrollPane pane = find("editor-pane");
if(pane != null) pane.setWidget(rebuildEditor());
}
Table rebuildRule() {
return new Table(table -> {
table.top().left();
table.table(rules -> {
rules.top().left();
Label label = rules.add("Block Health: ").get();
Slider slider = new Slider(0, 100, 1, false);
slider.changed(() -> {
label.setText("Block Health: "+(int)slider.getValue()+"%");
});
slider.change();
slider.moved(hp->Groups.build.each(b->b.health(b.block.health*hp/100)));
rules.add(slider);
}).grow();
});
}
Table rebuildEditor() {
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-> this.buildBlockSelection(null, select, blocks, ()-> drawBlock, block-> drawBlock =block, false)).marginTop(16f).marginBottom(16f).row();
table.image().height(4f).color(Pal.gray).growX().row();
table.table(select-> this.buildTeamSelection(player.team(), select, Seq.with(Team.all), ()->drawTeam, block->drawTeam=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<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(EditorTool.line);
addTool.get(EditorTool.pencil);
addTool.get(EditorTool.eraser);
addTool.get(EditorTool.fill);
addTool.get(EditorTool.spray);
ImageButton grid = new ImageButton(Icon.grid, Styles.clearTogglei);
grid.clicked(() -> {
grid.toggle();
Core.settings.put("grid", !Core.settings.getBool("grid"));
});
grid.update(()->grid.setChecked(Core.settings.getBool("grid")));
bt.add(grid);
});
tools.row();
Slider slider = new Slider(0, MapEditor.brushSizes.length - 1, 1, false);
slider.moved(f -> brushSize = MapEditor.brushSizes[(int)f]);
for(int j = 0; j < MapEditor.brushSizes.length; j++){
if(MapEditor.brushSizes[j] == brushSize){
slider.setValue(j);
}
}
Label label = new Label("Brush: "+brushSize);
label.touchable = Touchable.disabled;
tools.stack(slider, label).width(getDisplayWidth()/5).center();
}).left().width(getDisplayWidth() / 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-> {
if(tool==null) return;
option.top().left();
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.clearTogglei);
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)).margin(12f).growX().row();
}
}).grow();
}).left().width(getDisplayWidth() / 2).margin(8f).growY();
}).grow();
});
}
<T extends Block> void buildBlockSelection(@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 row = 4;
int max = Math.max(row, Math.round(getDisplayWidth()/2/8/row));
for(T item : items){
if(!item.unlockedNow()) continue;
ImageButton button = cont.button(Tex.whiteui, Styles.clearTogglei, 24, () -> {
if(closeSelect) control.input.config.hideConfig();
}).group(group).tooltip(t->t.background(Styles.black8).add(item.localizedName.replace(search.getText(), "[accent]"+search.getText()+"[]"))).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);
pane.setScrollYForce(blockScroll);
pane.update(() -> blockScroll = pane.getScrollY());
pane.setOverscroll(false, false);
table.add(pane).maxHeight(Scl.scl(row * 10 * 5));
}
float blockScroll;
<T extends Team> void buildTeamSelection(@Nullable Team team, 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 row = 2;
int max = Math.max(row, Math.round(getDisplayWidth()/2/8/row/2));
for(T item : items){
ImageButton button = cont.button(Tex.whiteui, Styles.clearTogglei, 24, () -> {
if(closeSelect) control.input.config.hideConfig();
}).group(group).tooltip(t->t.background(Styles.black8).add(item.localized().replace(search.getText(), "[accent]"+search.getText()+"[]"))).with(img -> img.getStyle().imageUpColor = item.color).get();
button.changed(() -> consumer.get(button.isChecked() ? item : null));
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);
pane.setScrollYForce(teamScroll);
pane.update(() -> teamScroll = pane.getScrollY());
pane.setOverscroll(false, false);
table.add(pane).maxHeight(Scl.scl(row * 10 * 5));
}
float teamScroll;
float getDisplayWidth() {
return window.getWidth() - (window.find("buttons") == null ? 1 : window.find("buttons").getWidth());
}
public void drawBlocksReplace(int x, int y){
drawBlocks(x, y, tile -> tile.block() != Blocks.air || drawBlock.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(drawBlock.isMultiblock()){
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, world.width() - drawBlock.size / 2 - 1);
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, world.height() - drawBlock.size / 2 - 1);
if(!hasOverlap(x, y)){
world.tile(x, y).setBlock(drawBlock, drawTeam, rotation);
}
}else{
boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air;
Cons<Tile> drawer = tile -> {
if(!tester.get(tile)) return;
if(isFloor){
tile.setFloor(drawBlock.asFloor());
}else if(!(tile.block().isMultiblock() && !drawBlock.isMultiblock())){
tile.setBlock(drawBlock, 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() != drawBlock && tile.block().size == drawBlock.size && tile.x == x && tile.y == y){
return false;
}
//else, check for overlap
int offsetx = -(drawBlock.size - 1) / 2;
int offsety = -(drawBlock.size - 1) / 2;
for(int dx = 0; dx < drawBlock.size; dx++){
for(int dy = 0; dy < drawBlock.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;
}
}