mirror of
https://github.com/yawaflua/Informatis.git
synced 2025-12-10 12:09:27 +02:00
469 lines
19 KiB
Java
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;
|
|
}
|
|
}
|