diff --git a/core/assets/maps/arena.png b/core/assets/maps/arena.png deleted file mode 100644 index 9755ab4f8a..0000000000 Binary files a/core/assets/maps/arena.png and /dev/null differ diff --git a/core/assets/maps/blankmap.png b/core/assets/maps/blankmap.png deleted file mode 100644 index 52073f2a27..0000000000 Binary files a/core/assets/maps/blankmap.png and /dev/null differ diff --git a/core/assets/maps/caldera.png b/core/assets/maps/caldera.png deleted file mode 100644 index c10907aadb..0000000000 Binary files a/core/assets/maps/caldera.png and /dev/null differ diff --git a/core/assets/maps/canyon.png b/core/assets/maps/canyon.png deleted file mode 100644 index 7240a5a5f5..0000000000 Binary files a/core/assets/maps/canyon.png and /dev/null differ diff --git a/core/assets/maps/caves.png b/core/assets/maps/caves.png deleted file mode 100644 index 1fc4865796..0000000000 Binary files a/core/assets/maps/caves.png and /dev/null differ diff --git a/core/assets/maps/delta.png b/core/assets/maps/delta.png deleted file mode 100644 index c04d38eb49..0000000000 Binary files a/core/assets/maps/delta.png and /dev/null differ diff --git a/core/assets/maps/desert.png b/core/assets/maps/desert.png deleted file mode 100644 index 99488439a3..0000000000 Binary files a/core/assets/maps/desert.png and /dev/null differ diff --git a/core/assets/maps/fortress.png b/core/assets/maps/fortress.png deleted file mode 100644 index 6b977c13de..0000000000 Binary files a/core/assets/maps/fortress.png and /dev/null differ diff --git a/core/assets/maps/grassland.png b/core/assets/maps/grassland.png deleted file mode 100644 index 1c7e8060fe..0000000000 Binary files a/core/assets/maps/grassland.png and /dev/null differ diff --git a/core/assets/maps/island.png b/core/assets/maps/island.png deleted file mode 100644 index 1e5d911afc..0000000000 Binary files a/core/assets/maps/island.png and /dev/null differ diff --git a/core/assets/maps/maps.json b/core/assets/maps/maps.json deleted file mode 100644 index 6150de5f22..0000000000 --- a/core/assets/maps/maps.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "maps": [ - { - "id": 0, - "name": "maze", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 1, - "name": "fortress", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 2, - "name": "sinkhole", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 3, - "name": "caves", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 4, - "name": "volcano", - "visible": true, - "flipBase": true, - "backgroundColor": "646464" - }, - { - "id": 5, - "name": "caldera", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 6, - "name": "scorch", - "visible": true, - "flipBase": false, - "backgroundColor": "e5d8bb" - }, - { - "id": 7, - "name": "desert", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 8, - "name": "island", - "visible": true, - "flipBase": false, - "backgroundColor": "e5d8bb" - }, - { - "id": 9, - "name": "grassland", - "visible": true, - "flipBase": false, - "backgroundColor": "5ab464" - }, - { - "id": 10, - "name": "tundra", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 11, - "name": "spiral", - "visible": true, - "flipBase": false, - "backgroundColor": "f7feff" - }, - { - "id": 12, - "name": "tutorial", - "visible": false, - "flipBase": false, - "backgroundColor": "646464" - } - ] -} diff --git a/core/assets/maps/maze.png b/core/assets/maps/maze.png deleted file mode 100644 index 018f94a1e0..0000000000 Binary files a/core/assets/maps/maze.png and /dev/null differ diff --git a/core/assets/maps/oilrush.png b/core/assets/maps/oilrush.png deleted file mode 100644 index 43d4e2f851..0000000000 Binary files a/core/assets/maps/oilrush.png and /dev/null differ diff --git a/core/assets/maps/pit.png b/core/assets/maps/pit.png deleted file mode 100644 index 58cc2fcc29..0000000000 Binary files a/core/assets/maps/pit.png and /dev/null differ diff --git a/core/assets/maps/scorch.png b/core/assets/maps/scorch.png deleted file mode 100644 index 6ad8e95514..0000000000 Binary files a/core/assets/maps/scorch.png and /dev/null differ diff --git a/core/assets/maps/sinkhole.png b/core/assets/maps/sinkhole.png deleted file mode 100644 index ebaffda376..0000000000 Binary files a/core/assets/maps/sinkhole.png and /dev/null differ diff --git a/core/assets/maps/spiral.png b/core/assets/maps/spiral.png deleted file mode 100644 index dfe43fcbb4..0000000000 Binary files a/core/assets/maps/spiral.png and /dev/null differ diff --git a/core/assets/maps/test1.png b/core/assets/maps/test1.png deleted file mode 100644 index 88ab432825..0000000000 Binary files a/core/assets/maps/test1.png and /dev/null differ diff --git a/core/assets/maps/test2.png b/core/assets/maps/test2.png deleted file mode 100644 index 0596dd6a00..0000000000 Binary files a/core/assets/maps/test2.png and /dev/null differ diff --git a/core/assets/maps/test3.png b/core/assets/maps/test3.png deleted file mode 100644 index 8209a06331..0000000000 Binary files a/core/assets/maps/test3.png and /dev/null differ diff --git a/core/assets/maps/tundra.png b/core/assets/maps/tundra.png deleted file mode 100644 index b64cc2b19f..0000000000 Binary files a/core/assets/maps/tundra.png and /dev/null differ diff --git a/core/assets/maps/tutorial.png b/core/assets/maps/tutorial.png deleted file mode 100644 index 88b8a8af04..0000000000 Binary files a/core/assets/maps/tutorial.png and /dev/null differ diff --git a/core/assets/maps/volcano.png b/core/assets/maps/volcano.png deleted file mode 100644 index dc4f6ba866..0000000000 Binary files a/core/assets/maps/volcano.png and /dev/null differ diff --git a/core/assets/version.properties b/core/assets/version.properties index 278f49ba23..52321f5850 100644 --- a/core/assets/version.properties +++ b/core/assets/version.properties @@ -1,5 +1,5 @@ #Autogenerated file. Do not modify. -#Sat Mar 17 21:20:30 EDT 2018 +#Sat Mar 17 21:49:13 EDT 2018 version=release androidBuildCode=538 name=Mindustry diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index a54252e78f..5aa8279761 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.utils.Align; import io.anuke.mindustry.Vars; +import io.anuke.mindustry.editor.MapEditorDialog; import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.fragments.*; @@ -43,7 +44,7 @@ public class UI extends SceneModule{ public PausedDialog paused; public SettingsMenuDialog settings; public ControlsDialog controls; - public FloatingDialog editor; //TODO change back to map editor dialog + public MapEditorDialog editor; public LanguageDialog language; public BansDialog bans; public AdminsDialog admins; @@ -143,7 +144,7 @@ public class UI extends SceneModule{ @Override public void init(){ - (editor = new FloatingDialog("The editor is currently broken.")).addCloseButton(); + editor = new MapEditorDialog(); controls = new ControlsDialog(); restart = new RestartDialog(); join = new JoinDialog(); diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java new file mode 100755 index 0000000000..c2472e718d --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -0,0 +1,39 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.Disposable; +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.io.MapTileData.TileDataWriter; + +import java.nio.ByteBuffer; + +public class DrawOperation implements Disposable{ + /**Data to apply operation to.*/ + MapTileData data; + /**Format: + * position (int) + * packed data FROM (use TileDataWriter's read/write methods) + * packed data TO (use TileDataWriter's read/write methods) + */ + ByteBuffer operation; + TileDataWriter writer = new TileDataWriter(); + + public DrawOperation(MapTileData data){ + this.data = data; + } + + public void set(ByteBuffer operation) { + this.operation = operation; + } + + public void undo() { + //TODO implement + } + + public void redo() { + //TODO implement + } + + @Override + public void dispose() {} + +} diff --git a/core/src/io/anuke/mindustry/editor/EditorTool.java b/core/src/io/anuke/mindustry/editor/EditorTool.java new file mode 100644 index 0000000000..31c2cee5b5 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/EditorTool.java @@ -0,0 +1,94 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.IntSet; +import io.anuke.mindustry.io.MapTileData.TileDataWriter; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.types.Floor; + +import static io.anuke.mindustry.Vars.ui; + +public enum EditorTool{ + pick{ + public void touched(MapEditor editor, int x, int y){ + TileDataWriter writer = editor.getMap().readAt(x, y); + Block block = Block.getByID(writer.wall == 0 ? writer.floor : writer.wall); + editor.setDrawBlock(block); + ui.editor.updateSelectedBlock(); + } + }, + pencil{ + { + edit = true; + } + + public void touched(MapEditor editor, int x, int y){ + editor.draw(x, y); + } + }, + line{ + { + + } + + }, + fill{ + { + edit = true; + } + + public void touched(MapEditor editor, int x, int y){ + //TODO select floor/block properly instead of using this method! + boolean floor = editor.getDrawBlock() instanceof Floor; + + TileDataWriter writer = editor.getMap().readAt(x, y); + + byte dest = floor ? writer.floor : writer.wall; + + int width = editor.getMap().width(); + int height = editor.getMap().height(); + + IntSet set = new IntSet(); + IntArray points = new IntArray(); + points.add(asInt(x, y, editor.getMap().width())); + + while(points.size != 0){ + int pos = points.pop(); + int px = pos % width; + int py = pos / width; + set.add(pos); + + writer = editor.getMap().readAt(px, py); + + if((floor ? writer.floor : writer.wall) == dest){ + if(floor) + writer.floor = dest; + else + writer.wall = dest; + + editor.getMap().write(px, py, writer); + editor.renderer().updatePoint(px, py); + + if(px > 0 && !set.contains(asInt(px - 1, py, width))) points.add(asInt(px - 1, py, width)); + if(py > 0 && !set.contains(asInt(px, py - 1, width))) points.add(asInt(px, py - 1, width)); + if(px < width - 1 && !set.contains(asInt(px + 1, py, width))) points.add(asInt(px + 1, py, width)); + if(py < height - 1 && !set.contains(asInt(px, py + 1, width))) points.add(asInt(px, py + 1, width)); + } + } + } + + int asInt(int x, int y, int width){ + return x+y*width; + } + + boolean colorEquals(int a, int b){ + return a == b; + } + }, + zoom; + boolean edit; + + public void touched(MapEditor editor, int x, int y){ + + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java new file mode 100644 index 0000000000..2f14dd0056 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -0,0 +1,81 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.io.MapTileData.TileDataWriter; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.mindustry.world.blocks.types.Floor; +import io.anuke.ucore.util.Mathf; + +public class MapEditor{ + public static final int minMapSize = 128, maxMapSize = 512; + public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15}; + + private MapTileData map; + private MapRenderer renderer = new MapRenderer(this); + + private int brushSize = 1; + private Block drawBlock = Blocks.stone; + + public MapEditor(){ + + } + + public MapTileData getMap(){ + return map; + } + + public void beginEdit(MapTileData map){ + drawBlock = Blocks.stone; + this.map = map; + this.brushSize = 1; + } + + public Block getDrawBlock(){ + return drawBlock; + } + + public void setDrawBlock(Block block){ + this.drawBlock = block; + } + + public void setBrushSize(int size){ + this.brushSize = size; + } + + public int getBrushSize() { + return brushSize; + } + + public void draw(int dx, int dy){ + if(dx < 0 || dy < 0 || dx >= map.width() || dy >= map.height()){ + return; + } + + TileDataWriter writer = map.readAt(dx, dy); + if(drawBlock instanceof Floor){ + writer.floor = (byte)drawBlock.id; + }else{ + writer.wall = (byte)drawBlock.id; + } + + for(int rx = -brushSize + 1; rx <= brushSize - 1; rx ++){ + for(int ry = -brushSize + 1; ry <= brushSize - 1; ry ++){ + if(Mathf.dst(rx, ry) < brushSize){ + map.write(dx + rx, dy + ry, writer); + renderer.updatePoint(dx + rx, dy + ry); + } + } + } + + } + + public MapRenderer renderer() { + return renderer; + } + + public void resize(int width, int height){ + map = new MapTileData(width, height); + renderer.resize(width, height); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java new file mode 100644 index 0000000000..8988e71272 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -0,0 +1,421 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.Input.Keys; +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.io.Platform; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.ColorMapper; +import io.anuke.mindustry.world.ColorMapper.BlockPair; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.Element; +import io.anuke.ucore.scene.builders.build; +import io.anuke.ucore.scene.builders.imagebutton; +import io.anuke.ucore.scene.builders.label; +import io.anuke.ucore.scene.builders.table; +import io.anuke.ucore.scene.ui.*; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; + +import static io.anuke.mindustry.Vars.gwt; +import static io.anuke.mindustry.Vars.ui; + +public class MapEditorDialog extends Dialog{ + private MapEditor editor; + private MapView view; + private MapGenerateDialog dialog; + private MapLoadDialog loadDialog; + private MapSaveDialog saveDialog; + private MapResizeDialog resizeDialog; + private ScrollPane pane; + //private FileChooser openFile, saveFile; + private boolean saved = false; + + private ButtonGroup blockgroup; + + public MapEditorDialog(){ + super("$text.mapeditor", "dialog"); + if(gwt) return; + + editor = new MapEditor(); + dialog = new MapGenerateDialog(editor); + view = new MapView(editor); + /* + openFile = new FileChooser("$text.loadimage", FileChooser.pngFilter, true, file -> { + ui.loadfrag.show(); + Timers.run(3f, () -> { + try{ + Pixmap pixmap = new Pixmap(file); + if(verifySize(pixmap)){ + editor.setPixmap(pixmap); + view.clearStack(); + }else{ + ui.showError(Bundles.format("text.editor.badsize", Arrays.toString(MapEditor.validMapSizes))); + } + }catch (Exception e){ + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + Log.err(e); + } + ui.loadfrag.hide(); + }); + }); + + saveFile = new FileChooser("$saveimage", false, file -> { + if(!file.extension().toLowerCase().equals(".png")){ + file = file.parent().child(file.nameWithoutExtension() + ".png"); + } + FileHandle result = file; + ui.loadfrag.show(); + Timers.run(3f, () -> { + try{ + Pixmaps.write(editor.pixmap(), result); + }catch (Exception e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + if(!android) Log.err(e); + } + ui.loadfrag.hide(); + }); + });*/ + /* + loadDialog = new MapLoadDialog(map -> { + saveDialog.setFieldText(map.name); + ui.loadfrag.show(); + + Timers.run(3f, () -> { + Map copy = new Map(); + copy.name = map.name; + copy.id = -1; + copy.pixmap = Pixmaps.copy(map.pixmap); + copy.texture = new Texture(copy.pixmap); + copy.oreGen = map.oreGen; + editor.beginEdit(copy); + ui.loadfrag.hide(); + view.clearStack(); + }); + });*/ + + resizeDialog = new MapResizeDialog(editor, (x, y) -> { + if(!(editor.getMap().width() == x && editor.getMap().height() == y)){ + ui.loadfrag.show(); + Timers.run(10f, () -> { + editor.resize(x, y); + view.clearStack(); + ui.loadfrag.hide(); + }); + } + }); + + /* + saveDialog = new MapSaveDialog(name -> { + ui.loadfrag.show(); + if(verifyMap()){ + saved = true; + String before = editor.getMap().name; + editor.getMap().name = name; + Timers.run(10f, () -> { + world.maps().saveAndReload(editor.getMap(), editor.pixmap()); + loadDialog.rebuild(); + ui.loadfrag.hide(); + view.clearStack(); + + if(!name.equals(before)) { + Map map = new Map(); + map.name = editor.getMap().name; + map.oreGen = editor.getMap().oreGen; + map.pixmap = Pixmaps.copy(editor.getMap().pixmap); + map.texture = new Texture(map.pixmap); + map.custom = true; + editor.beginEdit(map); + } + }); + + }else{ + ui.loadfrag.hide(); + } + });*/ + + setFillParent(true); + + clearChildren(); + margin(0); + build.begin(this); + build(); + build.end(); + + tapped(() -> { + Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true); + if(e == null || !e.isDescendantOf(pane)) Core.scene.setScrollFocus(null); + }); + + update(() -> { + if(Core.scene != null && Core.scene.getKeyboardFocus() == this){ + doInput(); + } + }); + + shown(() -> { + saved = true; + editor.beginEdit(new MapTileData(256, 256)); + blockgroup.getButtons().get(2).setChecked(true); + Core.scene.setScrollFocus(view); + view.clearStack(); + + Timers.runTask(10f, Platform.instance::updateRPC); + }); + + hidden(() -> Platform.instance.updateRPC()); + } + + public MapView getView() { + return view; + } + + public void resetSaved(){ + saved = false; + } + + public void updateSelectedBlock(){ + Block block = editor.getDrawBlock(); + int i = 0; + for(BlockPair pair : ColorMapper.getPairs()){ + if(pair.wall == block || (pair.wall == Blocks.air && pair.floor == block)){ + blockgroup.getButtons().get(i).setChecked(true); + break; + } + i++; + } + } + + public boolean hasPane(){ + return Core.scene.getScrollFocus() == pane; + } + + public void build(){ + + new table(){{ + float isize = 16*2f; + aleft(); + + new table(){{ + + defaults().growY().width(130f).padBottom(-6); + + new imagebutton("icon-terrain", isize, () -> + dialog.show() + ).text("$text.editor.generate"); + + row(); + + new imagebutton("icon-resize", isize, () -> + resizeDialog.show() + ).text("$text.editor.resize").padTop(4f); + + row(); + + new imagebutton("icon-load-map", isize, () -> + loadDialog.show() + ).text("$text.editor.loadmap"); + + row(); + + new imagebutton("icon-save-map", isize, ()-> + saveDialog.show() + ).text("$text.editor.savemap"); + + row(); + /* + new imagebutton("icon-load-image", isize, () -> + openFile.show() + ).text("$text.editor.loadimage"); + + row(); + + new imagebutton("icon-save-image", isize, () -> + saveFile.show() + ).text("$text.editor.saveimage");*/ + + row(); + + new imagebutton("icon-back", isize, () -> { + if(!saved){ + ui.showConfirm("$text.confirm", "$text.editor.unsaved", + MapEditorDialog.this::hide); + }else{ + hide(); + } + }).padBottom(0).text("$text.back"); + + }}.left().growY().end(); + + new table("button"){{ + add(view).grow(); + }}.grow().end(); + + new table(){{ + Table tools = new Table("button"); + tools.top(); + tools.marginTop(0).marginBottom(6); + + ButtonGroup group = new ButtonGroup<>(); + int i = 1; + + tools.defaults().size(53f, 58f).padBottom(-6); + + ImageButton undo = tools.addImageButton("icon-undo", 16*2f, () -> view.undo()).get(); + ImageButton redo = tools.addImageButton("icon-redo", 16*2f, () -> view.redo()).get(); + ImageButton grid = tools.addImageButton("icon-grid", "toggle", 16*2f, () -> view.setGrid(!view.isGrid())).get(); + + undo.setDisabled(() -> !view.getStack().canUndo()); + redo.setDisabled(() -> !view.getStack().canRedo()); + + undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE)); + redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE)); + grid.update(() -> grid.setChecked(view.isGrid())); + + for(EditorTool tool : EditorTool.values()){ + ImageButton button = new ImageButton("icon-" + tool.name(), "toggle"); + button.clicked(() -> view.setTool(tool)); + button.resizeImage(16*2f); + button.update(() -> button.setChecked(view.getTool() == tool)); + group.add(button); + if (tool == EditorTool.pencil) + button.setChecked(true); + + tools.add(button).padBottom(-6f); + if(i++ % 4 == 1) tools.row(); + } + + add(tools).width(53*4).padBottom(-6); + + row(); + + new table("button"){{ + margin(10f); + Slider slider = new Slider(0, MapEditor.brushSizes.length-1, 1, false); + slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int)(float)f])); + new label(() -> Bundles.format("text.editor.brushsize", MapEditor.brushSizes[(int)slider.getValue()])).left(); + row(); + add(slider).growX().padTop(4f); + }}.growX().padBottom(-6).end(); + + row(); + + /* + new table("button"){{ + get().addCheck("$text.oregen", b -> editor.getMap().oreGen = b) + .update(c -> c.setChecked(editor.getMap().oreGen)).padTop(3).padBottom(3); + }}.growX().padBottom(-6).end();*/ + + row(); + + addBlockSelection(get()); + + row(); + + }}.right().growY().end(); + }}.grow().end(); + } + + private void doInput(){ + //tool select + for(int i = 0; i < EditorTool.values().length; i ++){ + int code = i == 0 ? 5 : i; + if(Inputs.keyTap("weapon_" + code)){ + view.setTool(EditorTool.values()[i]); + break; + } + } + + //ctrl keys (undo, redo, save) + if(Inputs.keyDown(Keys.CONTROL_LEFT)){ + if(Inputs.keyTap(Keys.Z)){ + view.undo(); + } + + if(Inputs.keyTap(Keys.Y)){ + view.redo(); + } + + if(Inputs.keyTap(Keys.S)){ + saveDialog.save(); + } + + if(Inputs.keyTap(Keys.G)){ + view.setGrid(!view.isGrid()); + } + } + } + + private boolean verifyMap(){ + //TODO make sure both teams have cores or something + /* + int psc = ColorMapper.getColor(SpecialBlocks.playerSpawn); + int esc = ColorMapper.getColor(SpecialBlocks.enemySpawn); + + int playerSpawns = 0; + int enemySpawns = 0; + Pixmap pix = editor.pixmap(); + + for(int x = 0; x < pix.getWidth(); x ++){ + for(int y = 0; y < pix.getHeight(); y ++){ + int i = pix.getPixel(x, y); + if(i == psc) playerSpawns ++; + if(i == esc) enemySpawns ++; + } + } + + if(playerSpawns == 0){ + ui.showError("$text.editor.noplayerspawn"); + return false; + }else if(playerSpawns > 1){ + ui.showError("$text.editor.manyplayerspawns"); + return false; + } + + if(enemySpawns > MapEditor.maxSpawnpoints){ + ui.showError(Bundles.format("text.editor.manyenemyspawns", MapEditor.maxSpawnpoints)); + return false; + }*/ + + return true; + } + + private void addBlockSelection(Table table){ + Table content = new Table(); + pane = new ScrollPane(content, "volume"); + pane.setScrollingDisabled(true, false); + pane.setFadeScrollBars(false); + pane.setOverscroll(true, false); + ButtonGroup group = new ButtonGroup<>(); + blockgroup = group; + + int i = 0; + + for(BlockPair pair : ColorMapper.getPairs()){ + Block block = pair.wall == Blocks.air ? pair.floor : pair.wall; + + ImageButton button = new ImageButton(Draw.hasRegion(block.name) ? Draw.region(block.name) : Draw.region(block.name + "1"), "toggle"); + button.clicked(() -> editor.setDrawBlock(block)); + button.resizeImage(8*4f); + group.add(button); + content.add(button).pad(4f).size(53f, 58f); + + if(i++ % 2 == 1){ + content.row(); + } + } + + group.getButtons().get(2).setChecked(true); + + Table extra = new Table("button"); + extra.labelWrap(() -> editor.getDrawBlock().formalName).width(180f).center(); + table.add(extra).padBottom(-6).growX(); + table.row(); + table.add(pane).growY().fillX(); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapFilter.java b/core/src/io/anuke/mindustry/editor/MapFilter.java new file mode 100644 index 0000000000..a01b082a43 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapFilter.java @@ -0,0 +1,244 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.OrderedMap; + +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.ColorMapper; +import io.anuke.mindustry.world.ColorMapper.BlockPair; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.mindustry.world.blocks.types.Floor; +import io.anuke.ucore.graphics.Pixmaps; +import io.anuke.ucore.noise.RidgedPerlin; +import io.anuke.ucore.noise.Simplex; +import io.anuke.ucore.util.Mathf; + +public class MapFilter{ + private ObjectMap prefs = map( + pref("replace", "whether to replace blocks"), + pref("terrain", "generate new terrain"), + pref("circle", "generate terrain in a circle"), + pref("distort", "distort the map image"), + pref("sand", "add patches of sand"), + pref("grass", "add patches of grass"), + pref("stone", "add patches of stone"), + pref("blackstone", "add patches of black stone"), + pref("allgrass", "fill map with grass"), + pref("allsnow", "fill map with snow"), + pref("allsand", "fill map with sand"), + pref("water", "add lakes"), + pref("oil", "add oil lakes"), + pref("lavariver", "add lava rivers"), + pref("slavariver", "ad small lava rivers"), + pref("river", "add rivers"), + pref("iceriver", "add frozen rivers"), + pref("oilriver", "add oil rivers") + ); + + private Simplex sim = new Simplex(); + private RidgedPerlin rid = new RidgedPerlin(1, 10, 20f); + private RidgedPerlin rid2 = new RidgedPerlin(1, 6, 1f); + private RidgedPerlin rid3 = new RidgedPerlin(1, 6, 1f); + + public MapFilter(){ + prefs.get("replace").enabled = true; + prefs.get("terrain").enabled = true; + randomize(); + } + + public void randomize(){ + sim.setSeed(Mathf.random(999999)); + rid.setSeed(Mathf.random(999999)); + rid2.setSeed(Mathf.random(999999)); + rid3.setSeed(Mathf.random(999999)); + } + + public ObjectMap getPrefs(){ + return prefs; + } + + public Pixmap process(Pixmap pixmap){ + if(prefs.get("terrain").enabled){ + for(int x = 0; x < pixmap.getWidth(); x++){ + for(int y = 0; y < pixmap.getHeight(); y++){ + float dist = Vector2.dst((float) x / pixmap.getWidth(), (float) y / pixmap.getHeight(), 0.5f, 0.5f) * 2f; + double noise = sim.octaveNoise2D(6, 0.6, 1 / 180.0, x, y + 9999) / (prefs.get("circle").enabled ? 1.7 : 1f) + dist / 10f; + + if(dist > 0.8){ + noise += 2 * (dist - 0.8); + } + + Block block = noise > 0.6 ? Blocks.stoneblock : Blocks.stone; + + pixmap.drawPixel(x, y, ColorMapper.getColor(block)); + } + } + } + + Pixmap src = Pixmaps.copy(pixmap); + + for(int x = 0; x < pixmap.getWidth(); x++){ + for(int y = 0; y < pixmap.getHeight(); y++){ + int dx = 0, dy = 0; + + if(prefs.get("distort").enabled){ + double intensity = 12; + double scale = 80; + double octaves = 4; + double falloff = 0.6; + double nx = (sim.octaveNoise2D(octaves, falloff, 1 / scale, x, y) - 0.5f) * intensity; + double ny = (sim.octaveNoise2D(octaves, falloff, 1 / scale, x, y + 99999) - 0.5f) * intensity; + dx = (int) nx; + dy = (int) ny; + } + + int pix = src.getPixel(x + dx, y + dy); + + BlockPair pair = ColorMapper.get(pix); + Block block = pair == null ? null : pair.wall == Blocks.air ? pair.floor : pair.wall; + + if(block == null) + continue; + + boolean floor = block instanceof Floor; + + double noise = sim.octaveNoise2D(4, 0.6, 1 / 170.0, x, y) + sim.octaveNoise2D(1, 1.0, 1 / 5.0, x, y) / 18.0; + double nwater = sim.octaveNoise2D(1, 1.0, 1 / 130.0, x, y); + noise += nwater / 5.0; + + double noil = sim.octaveNoise2D(1, 1.0, 1 / 150.0, x + 9999, y) + sim.octaveNoise2D(1, 1.0, 1 / 2.0, x, y) / 290.0; + + if(!floor || prefs.get("replace").enabled){ + + if(prefs.get("allgrass").enabled){ + block = floor ? Blocks.grass : Blocks.grassblock; + }else if(prefs.get("allsnow").enabled){ + block = floor ? Blocks.snow : Blocks.snowblock; + }else if(prefs.get("allsand").enabled){ + block = floor ? Blocks.sand : Blocks.sandblock; + }else if(prefs.get("replace").enabled){ + block = floor ? Blocks.stone : Blocks.stoneblock; + } + + if(noise > 0.7 && prefs.get("grass").enabled){ + block = floor ? Blocks.grass : Blocks.grassblock; + } + if(noise > 0.7 && prefs.get("blackstone").enabled){ + block = floor ? Blocks.blackstone : Blocks.blackstoneblock; + } + if(noise > 0.7 && prefs.get("sand").enabled){ + block = floor ? Blocks.sand : Blocks.sandblock; + } + if(noise > 0.8 && prefs.get("stone").enabled){ + block = floor ? Blocks.stone : Blocks.stoneblock; + } + } + + if(floor){ + if(nwater > 0.93 && prefs.get("water").enabled){ + block = Blocks.water; + if(nwater > 0.943){ + block = Blocks.deepwater; + } + } + + if(noil > 0.95 && prefs.get("oil").enabled){ + block = Blocks.dirt; + if(noil > 0.955){ + block = Blocks.oil; + } + } + } + + if(floor && prefs.get("lavariver").enabled){ + double lava = rid.getValue(x, y, 1 / 100f); + double t = 0.6; + if(lava > t){ + block = Blocks.lava; + }else if(lava > t - 0.2){ + block = Blocks.blackstone; + } + } + + if(floor && prefs.get("slavariver").enabled){ + double lava = rid.getValue(x, y, 1 / 40f); + double t = 0.7; + if(lava > t){ + block = Blocks.lava; + }else if(lava > t - 0.3){ + block = Blocks.blackstone; + } + } + + if(floor && prefs.get("oilriver").enabled){ + double lava = rid3.getValue(x, y, 1 / 100f); + double t = 0.9; + if(lava > t){ + block = Blocks.oil; + }else if(lava > t - 0.2){ + block = Blocks.dirt; + } + } + + if(floor && prefs.get("river").enabled){ + double riv = rid2.getValue(x, y, 1 / 140f); + double t = 0.4; + + if(riv > t + 0.1){ + block = Blocks.deepwater; + }else if(riv > t){ + block = Blocks.water; + }else if(riv > t - 0.2){ + block = Blocks.grass; + } + } + + if(floor && prefs.get("iceriver").enabled){ + double riv = rid2.getValue(x, y, 1 / 140f); + double t = 0.4; + + if(riv > t + 0.1){ + block = Blocks.ice; + }else if(riv > t){ + block = Blocks.ice; + }else if(riv > t - 0.2){ + block = Blocks.snow; + } + } + + pixmap.drawPixel(x, y, ColorMapper.getColor(block)); + } + } + + src.dispose(); + + return pixmap; + } + + private static OrderedMap map(GenPref...objects){ + OrderedMap prefs = new OrderedMap<>(); + + for(int i = 0; i < objects.length; i ++){ + GenPref pref = (GenPref)objects[i]; + prefs.put(pref.name, pref); + } + return prefs; + } + + private GenPref pref(String name, String desc){ + return new GenPref(name, desc); + } + + class GenPref{ + public final String name; + public final String description; + public boolean enabled; + + GenPref(String name, String description){ + this.name = name; + this.description = description; + } + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java new file mode 100644 index 0000000000..4ed34dbd56 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java @@ -0,0 +1,105 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Scaling; + +import static io.anuke.mindustry.Vars.*; +import io.anuke.mindustry.editor.MapFilter.GenPref; +import io.anuke.mindustry.ui.BorderImage; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Pixmaps; +import io.anuke.ucore.scene.style.TextureRegionDrawable; +import io.anuke.ucore.scene.ui.CheckBox; +import io.anuke.ucore.scene.ui.Image; +import io.anuke.ucore.scene.ui.ScrollPane; +import io.anuke.ucore.scene.ui.layout.Stack; +import io.anuke.ucore.scene.ui.layout.Table; + +public class MapGenerateDialog extends FloatingDialog{ + private MapEditor editor; + private Image image; + private boolean loading; + + public MapGenerateDialog(MapEditor editor) { + super("$text.editor.generate"); + this.editor = editor; + + Stack stack = new Stack(); + stack.add(image = new BorderImage()); + + Image loadImage = new Image("icon-loading"); + loadImage.setScaling(Scaling.none); + loadImage.setScale(3f); + loadImage.update(() -> loadImage.setOrigin(Align.center)); + loadImage.setVisible(() -> loading); + Image next = new Image("white"); + next.setScaling(Scaling.fit); + next.setColor(0, 0, 0, 0.6f); + next.setVisible(() -> loading); + + stack.add(next); + stack.add(loadImage); + + content().add(stack).grow(); + image.setScaling(Scaling.fit); + Table preft = new Table(); + preft.left(); + preft.margin(4f).marginRight(25f); + + for(GenPref pref : editor.getFilter().getPrefs().values()){ + CheckBox box = new CheckBox(pref.name); + box.setChecked(pref.enabled); + box.changed(() -> pref.enabled = box.isChecked()); + preft.add(box).pad(4f).left(); + preft.row(); + } + + ScrollPane pane = new ScrollPane(preft, "volume"); + pane.setFadeScrollBars(false); + pane.setScrollingDisabled(true, false); + + content().add(pane).fillY(); + + buttons().defaults().size(170f, 50f).pad(4f); + buttons().addButton("$text.back", this::hide); + buttons().addButton("$text.randomize", () ->{ + editor.getFilter().randomize(); + apply(); + }); + buttons().addButton("$text.update", this::apply); + buttons().addButton("$text.apply", () ->{ + ui.loadfrag.show(); + + Timers.run(3f, () ->{ + Pixmap copy = Pixmaps.copy(editor.pixmap()); + editor.applyFilter(); + ui.editor.getView().push(copy, Pixmaps.copy(editor.pixmap())); + ui.loadfrag.hide(); + ui.editor.resetSaved(); + hide(); + }); + }); + + shown(() ->{ + loading = true; + Timers.run(30f, () -> { + editor.applyFilterPreview(); + image.setDrawable(new TextureRegionDrawable(new TextureRegion(editor.getFilterTexture()))); + loading = false; + }); + }); + } + + private void apply(){ + loading = true; + Timers.run(3f, () -> { + editor.applyFilterPreview(); + loading = false; + }); + + } + +} diff --git a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java new file mode 100644 index 0000000000..7a7495db4a --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java @@ -0,0 +1,73 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.ui.BorderImage; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.mindustry.world.Map; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.scene.ui.ButtonGroup; +import io.anuke.ucore.scene.ui.ScrollPane; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.layout.Table; + +import static io.anuke.mindustry.Vars.world; + +public class MapLoadDialog extends FloatingDialog{ + private Map selected = world.maps().getMap(0); + + public MapLoadDialog(Consumer loader) { + super("$text.editor.loadmap"); + + shown(this::rebuild); + rebuild(); + + TextButton button = new TextButton("$text.load"); + button.setDisabled(() -> selected == null); + button.clicked(() -> { + if (selected != null) { + loader.accept(selected); + hide(); + } + }); + + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().add(button); + } + + public void rebuild(){ + content().clear(); + + selected = world.maps().getMap(0); + + ButtonGroup group = new ButtonGroup<>(); + + int maxcol = 3; + + int i = 0; + + Table table = new Table(); + table.defaults().size(200f, 90f).pad(4f); + table.margin(10f); + + ScrollPane pane = new ScrollPane(table, "horizontal"); + pane.setFadeScrollBars(false); + + for (Map map : world.maps().list()) { + if (!map.visible) continue; + + TextButton button = new TextButton(map.localized(), "toggle"); + button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); + button.getCells().reverse(); + button.clicked(() -> selected = map); + button.getLabelCell().grow().left().padLeft(5f); + group.add(button); + table.add(button); + if (++i % maxcol == 0) table.row(); + } + + content().add("$text.editor.loadmap"); + content().row(); + content().add(pane); + } + +} diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java new file mode 100644 index 0000000000..857575ec66 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -0,0 +1,103 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.utils.IntSet; +import com.badlogic.gdx.utils.IntSet.IntSetIterator; +import io.anuke.mindustry.io.MapTileData.TileDataWriter; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.graphics.CacheBatch; +import io.anuke.ucore.graphics.Draw; + +import static io.anuke.mindustry.Vars.tilesize; + +public class MapRenderer { + private static final int chunksize = 32; + private CacheBatch batch; + private int[][] chunks; + private IntSet updates = new IntSet(); + private MapEditor editor; + + public MapRenderer(MapEditor editor){ + this.editor = editor; + } + + public void resize(int width, int height){ + batch = new CacheBatch(width * height * 3); + chunks = new int[width / chunksize][height / chunksize]; + updates.clear(); + updateAll(); + } + + public void draw(){ + Graphics.end(); + Graphics.useBatch(batch); + + IntSetIterator it = updates.iterator(); + int i = it.next(); + for(; it.hasNext; i = it.next()){ + int x = i % chunks.length; + int y = i / chunks.length; + render(x, y, chunks[x][y]); + } + updates.clear(); + + Graphics.popBatch(); + + Gdx.gl.glEnable(GL20.GL_BLEND); + + batch.beginDraw(); + + for(int x = 0; x < chunks.length; x ++){ + for(int y = 0; y < chunks[0].length; y ++){ + int id = chunks[x][y]; + batch.drawCache(id); + } + } + + batch.endDraw(); + + Graphics.begin(); + } + + public void updatePoint(int x, int y){ + x /= chunksize; + y /= chunksize; + updates.add(x + y * chunks.length); + } + + public void updateAll(){ + for(int x = 0; x < chunks.length; x ++){ + for(int y = 0; y < chunks[0].length; y ++){ + render(x, y, chunks[x][y]); + } + } + } + + private void render(int chunkx, int chunky, int previousID){ + if(previousID == -1){ + batch.begin(); + }else{ + batch.begin(previousID); + } + + for(int x = 0; x < chunkx; x ++){ + for(int y = 0; y < chunky; y ++){ + int wx = chunkx*chunksize + x; + int wy = chunky*chunksize + y; + + TileDataWriter data = editor.getMap().readAt(wx, wy); + Block floor = Block.getByID(data.floor); + Block wall = Block.getByID(data.wall); + + if(floor != Blocks.air) Draw.rect(floor.name, wx * tilesize, wy * tilesize); + if(floor != Blocks.air) Draw.rect(wall.name, wx * tilesize, wy * tilesize); + } + } + + batch.end(); + chunks[chunkx][chunky] = batch.getLastCache(); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapResizeDialog.java b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java new file mode 100644 index 0000000000..719b567463 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java @@ -0,0 +1,67 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.utils.Align; + +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.function.BiConsumer; +import io.anuke.ucore.scene.ui.ButtonGroup; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.layout.Table; + +public class MapResizeDialog extends FloatingDialog{ + int width, height; + + public MapResizeDialog(MapEditor editor, BiConsumer cons){ + super("$text.editor.resizemap"); + shown(() -> { + content().clear(); + Pixmap pix = editor.pixmap(); + width = pix.getWidth(); + height = pix.getHeight(); + + Table table = new Table(); + + for(int d = 0; d < 2; d ++){ + boolean w = d == 0; + int curr = d == 0 ? pix.getWidth() : pix.getHeight(); + int idx = 0; + for(int i = 0; i < MapEditor.validMapSizes.length; i ++) + if(MapEditor.validMapSizes[i] == curr) idx = i; + + table.add(d == 0 ? "$text.width": "$text.height").padRight(8f); + ButtonGroup group = new ButtonGroup<>(); + for(int i = 0; i < MapEditor.validMapSizes.length; i ++){ + int size = MapEditor.validMapSizes[i]; + TextButton button = new TextButton(size + "", "toggle"); + button.clicked(() -> { + if(w) + width = size; + else + height = size; + }); + group.add(button); + if(i == idx) button.setChecked(true); + table.add(button).size(100f, 54f).pad(2f); + } + + table.row(); + } + + content().label(() -> + width + height > 512 ? "$text.editor.resizebig" : "" + ).get().setAlignment(Align.center, Align.center); + content().row(); + content().add(table); + + }); + + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().addButton("$text.editor.resize", () -> { + cons.accept(width, height); + hide(); + }); + + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapSaveDialog.java b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java new file mode 100644 index 0000000000..acabbfa970 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java @@ -0,0 +1,75 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.Platform; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.TextField; + +import static io.anuke.mindustry.Vars.ui; +import static io.anuke.mindustry.Vars.world; + +public class MapSaveDialog extends FloatingDialog{ + private TextField field; + private Consumer listener; + + public MapSaveDialog(Consumer cons){ + super("$text.editor.savemap"); + field = new TextField(); + listener = cons; + + Platform.instance.addDialog(field); + + shown(() -> { + content().clear(); + content().label(() ->{ + Map map = world.maps().getByName(field.getText()); + if(map != null){ + if(map.custom){ + return "$text.editor.overwrite"; + }else{ + return "$text.editor.failoverwrite"; + } + } + return ""; + }).colspan(2); + content().row(); + content().add("$text.editor.mapname").padRight(14f); + content().add(field).size(220f, 48f); + }); + + buttons().defaults().size(200f, 50f).pad(2f); + buttons().addButton("$text.cancel", this::hide); + + TextButton button = new TextButton("$text.save"); + button.clicked(() -> { + if(!invalid()){ + cons.accept(field.getText()); + hide(); + } + }); + button.setDisabled(this::invalid); + buttons().add(button); + } + + public void save(){ + if(!invalid()){ + listener.accept(field.getText()); + }else{ + ui.showError("$text.editor.failoverwrite"); + } + } + + public void setFieldText(String text){ + field.setText(text); + } + + private boolean invalid(){ + if(field.getText().isEmpty()){ + return true; + } + Map map = world.maps().getByName(field.getText()); + return map != null && !map.custom; + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapView.java b/core/src/io/anuke/mindustry/editor/MapView.java new file mode 100644 index 0000000000..75f73e4267 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapView.java @@ -0,0 +1,328 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.Input.Keys; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Colors; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.input.GestureDetector; +import com.badlogic.gdx.input.GestureDetector.GestureListener; +import com.badlogic.gdx.math.Bresenham2; +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.ui.GridImage; +import io.anuke.mindustry.world.ColorMapper; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.graphics.Pixmaps; +import io.anuke.ucore.scene.Element; +import io.anuke.ucore.scene.event.InputEvent; +import io.anuke.ucore.scene.event.InputListener; +import io.anuke.ucore.scene.event.Touchable; +import io.anuke.ucore.scene.ui.TextField; +import io.anuke.ucore.scene.ui.layout.Unit; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Tmp; + +import static io.anuke.mindustry.Vars.ui; + +public class MapView extends Element implements GestureListener{ + private MapEditor editor; + private EditorTool tool = EditorTool.pencil; + private OperationStack stack = new OperationStack(); + private DrawOperation op; + private Pixmap current; + private Bresenham2 br = new Bresenham2(); + private boolean updated = false; + private float offsetx, offsety; + private float zoom = 1f; + private boolean grid = false; + private GridImage image = new GridImage(0, 0); + private Vector2 vec = new Vector2(); + private Rectangle rect = new Rectangle(); + + private boolean drawing; + private int lastx, lasty; + private int startx, starty; + + public void setTool(EditorTool tool){ + this.tool = tool; + } + + public EditorTool getTool() { + return tool; + } + + public void clearStack(){ + stack.clear(); + current = null; + } + + public OperationStack getStack() { + return stack; + } + + public void setGrid(boolean grid) { + this.grid = grid; + } + + public boolean isGrid() { + return grid; + } + + public void push(Pixmap previous, Pixmap add){ + DrawOperation op = new DrawOperation(editor.pixmap()); + op.add(previous, add); + stack.add(op); + this.current = add; + } + + public void undo(){ + if(stack.canUndo()){ + stack.undo(); + editor.updateTexture(); + } + } + + public void redo(){ + if(stack.canRedo()){ + stack.redo(); + editor.updateTexture(); + } + } + + public MapView(MapEditor editor){ + this.editor = editor; + + Inputs.addProcessor(0, new GestureDetector(20, 0.5f, 2, 0.15f, this)); + setTouchable(Touchable.enabled); + + addListener(new InputListener(){ + + @Override + public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { + if(pointer != 0){ + return false; + } + + if(current == null){ + current = Pixmaps.copy(editor.pixmap()); + } + updated = false; + + GridPoint2 p = project(x, y); + lastx = p.x; + lasty = p.y; + startx = p.x; + starty = p.y; + tool.touched(editor, p.x, p.y); + + if(tool.edit){ + updated = true; + ui.editor.resetSaved(); + } + + op = new DrawOperation(editor.pixmap()); + + drawing = true; + return true; + } + + @Override + public void touchUp (InputEvent event, float x, float y, int pointer, int button) { + drawing = false; + + GridPoint2 p = project(x, y); + + if(tool == EditorTool.line){ + ui.editor.resetSaved(); + Array points = br.line(startx, starty, p.x, p.y); + for(GridPoint2 point : points){ + editor.draw(point.x, point.y); + } + updated = true; + } + + if(updated){ + if(op == null) op = new DrawOperation(editor.pixmap()); + Pixmap next = Pixmaps.copy(editor.pixmap()); + op.add(current, next); + current = null; + stack.add(op); + op = null; + } + } + + @Override + public void touchDragged (InputEvent event, float x, float y, int pointer) { + GridPoint2 p = project(x, y); + + if(drawing && tool == EditorTool.pencil){ + ui.editor.resetSaved(); + Array points = br.line(lastx, lasty, p.x, p.y); + for(GridPoint2 point : points){ + editor.draw(point.x, point.y); + } + updated = true; + } + lastx = p.x; + lasty = p.y; + } + }); + } + + @Override + public void act(float delta){ + super.act(delta); + + if(Core.scene.getKeyboardFocus() == null || !(Core.scene.getKeyboardFocus() instanceof TextField) && + !Inputs.keyDown(Keys.CONTROL_LEFT)) { + float ax = Inputs.getAxis("move_x"); + float ay = Inputs.getAxis("move_y"); + offsetx -= ax * 15f / zoom; + offsety -= ay * 15f / zoom; + } + + if(ui.editor.hasPane()) return; + + zoom += Inputs.scroll()/10f * zoom; + clampZoom(); + } + + private void clampZoom(){ + zoom = Mathf.clamp(zoom, 0.2f, 12f); + } + + private GridPoint2 project(float x, float y){ + float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + x = (x - getWidth()/2 + sclwidth/2 - offsetx*zoom) / sclwidth * editor.texture().getWidth(); + y = (y - getHeight()/2 + sclheight/2 - offsety*zoom) / sclheight * editor.texture().getHeight(); + return Tmp.g1.set((int)x, editor.texture().getHeight() - 1 - (int)y); + } + + private Vector2 unproject(int x, int y){ + float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float px = ((float)x / editor.texture().getWidth()) * sclwidth + offsetx*zoom - sclwidth/2 + getWidth()/2; + float py = (float)((float)(editor.texture().getHeight() - 1 - y) / editor.texture().getHeight()) * sclheight + + offsety*zoom - sclheight/2 + getHeight()/2; + return vec.set(px, py); + } + + @Override + public void draw(Batch batch, float alpha){ + float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float centerx = x + width/2 + offsetx * zoom; + float centery = y + height/2 + offsety * zoom; + + image.setImageSize(editor.pixmap().getWidth(), editor.pixmap().getHeight()); + + batch.flush(); + boolean pop = ScissorStack.pushScissors(rect.set(x + width/2 - size/2, y + height/2 - size/2, size, size)); + + batch.draw(editor.texture(), centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); + + if(grid){ + Draw.color(Color.GRAY); + image.setBounds(centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); + image.draw(batch, alpha); + Draw.color(); + } + + if(tool == EditorTool.line && drawing){ + Vector2 v1 = unproject(startx, starty).add(x, y); + float sx = v1.x, sy = v1.y; + Vector2 v2 = unproject(lastx, lasty).add(x, y); + + Draw.color(Tmp.c1.set(ColorMapper.getColor(editor.getDrawBlock()))); + Lines.stroke(Unit.dp.scl(3f * zoom)); + Lines.line(sx, sy, v2.x, v2.y); + + Lines.poly(sx, sy, 40, editor.getBrushSize() * zoom * 3); + + Lines.poly(v2.x, v2.y, 40, editor.getBrushSize() * zoom * 3); + } + + batch.flush(); + + if(pop) ScissorStack.popScissors(); + + Draw.color(Colors.get("accent")); + Lines.stroke(Unit.dp.scl(3f)); + Lines.rect(x + width/2 - size/2, y + height/2 - size/2, size, size); + Draw.reset(); + } + + private boolean active(){ + return Core.scene.getKeyboardFocus() != null + && Core.scene.getKeyboardFocus().isDescendantOf(ui.editor) + && ui.editor.isShown() && tool == EditorTool.zoom && + Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true) == this; + } + + @Override + public boolean touchDown(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean tap(float x, float y, int count, int button){ + return false; + } + + @Override + public boolean longPress(float x, float y){ + return false; + } + + @Override + public boolean fling(float velocityX, float velocityY, int button){ + return false; + } + + @Override + public boolean pan(float x, float y, float deltaX, float deltaY){ + if(!active()) return false; + offsetx += deltaX / zoom; + offsety -= deltaY / zoom; + return false; + } + + @Override + public boolean panStop(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean zoom(float initialDistance, float distance){ + if(!active()) return false; + float nzoom = distance - initialDistance; + zoom += nzoom / 10000f / Unit.dp.scl(1f) * zoom; + clampZoom(); + return false; + } + + @Override + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ + return false; + } + + @Override + public void pinchStop(){ + + } +} diff --git a/core/src/io/anuke/mindustry/editor/OperationStack.java b/core/src/io/anuke/mindustry/editor/OperationStack.java new file mode 100755 index 0000000000..b09e4c48d9 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/OperationStack.java @@ -0,0 +1,54 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.Array; + +public class OperationStack{ + private final static int maxSize = 10; + private Array stack = new Array<>(); + private int index = 0; + + public OperationStack(){ + + } + + public void clear(){ + for(DrawOperation op : stack){ + op.dispose(); + } + stack.clear(); + index = 0; + } + + public void add(DrawOperation action){ + stack.truncate(stack.size + index); + index = 0; + stack.add(action); + + if(stack.size > maxSize){ + stack.removeIndex(0); + } + } + + public boolean canUndo(){ + return !(stack.size - 1 + index < 0); + } + + public boolean canRedo(){ + return !(index > -1 || stack.size + index < 0); + } + + public void undo(){ + if(!canUndo()) return; + + stack.get(stack.size - 1 + index).undo(); + index --; + } + + public void redo(){ + if(!canRedo()) return; + + index ++; + stack.get(stack.size - 1 + index).redo(); + + } +} diff --git a/core/src/io/anuke/mindustry/io/MapTileData.java b/core/src/io/anuke/mindustry/io/MapTileData.java index 95f75b2d23..effacfec94 100644 --- a/core/src/io/anuke/mindustry/io/MapTileData.java +++ b/core/src/io/anuke/mindustry/io/MapTileData.java @@ -12,7 +12,7 @@ public class MapTileData { private final static int TILE_SIZE = 3; private final ByteBuffer buffer; - private final TileData tile = new TileData(); + private final TileDataWriter tile = new TileDataWriter(); private final int width, height; public MapTileData(int width, int height){ @@ -36,15 +36,22 @@ public class MapTileData { } /**Reads and returns the next tile data.*/ - public TileData read(){ - tile.read(); + public TileDataWriter read(){ + tile.read(buffer); return tile; } - /**Writes tile data at a specified position. Uses the tile data returned by read().*/ - public void write(int x, int y){ + /**Reads and returns the next tile data.*/ + public TileDataWriter readAt(int x, int y){ position(x, y); - tile.write(); + tile.read(buffer); + return tile; + } + + /**Writes tile data at a specified position.*/ + public void write(int x, int y, TileDataWriter writer){ + position(x, y); + writer.write(buffer); } /**Sets read position to the specified coordinates*/ @@ -52,12 +59,12 @@ public class MapTileData { buffer.position((x + width * y) * TILE_SIZE); } - public class TileData{ + public static class TileDataWriter { public byte floor, wall; public byte rotation; public byte team; - private void read(){ + public void read(ByteBuffer buffer){ floor = buffer.get(); wall = buffer.get(); byte rt = buffer.get(); @@ -65,7 +72,7 @@ public class MapTileData { team = Bits.getRightByte(rt); } - private void write(){ + public void write(ByteBuffer buffer){ buffer.put(floor); buffer.put(wall); byte rt = Bits.packByte(rotation, team); diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index 92b23ecb94..4f0c793108 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -1,9 +1,11 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.ucore.util.Log; import java.io.DataInputStream; import java.io.IOException; @@ -13,30 +15,43 @@ import static io.anuke.mindustry.Vars.customMapDirectory; import static io.anuke.mindustry.Vars.mapExtension; public class Maps implements Disposable{ + /**List of all built-in maps.*/ + private static final String[] defaultMapNames = {}; + private ObjectMap maps = new ObjectMap<>(); private Array allMaps = new Array<>(); - private Array customMaps = new Array<>(); - private Array defaultMaps = new Array<>(); - - public void load(){ - //TODO - } - - public void save(){ - //TODO? - } + private Array returnArray = new Array<>(); /**Returns a list of all maps, including custom ones.*/ public Array all(){ return allMaps; } + /**Returns a list of only custo maps.*/ + public Array customMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(map.custom) returnArray.add(map); + } + return returnArray; + } + + /**Returns a list of only default maps.*/ + public Array defaultMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(!map.custom) returnArray.add(map); + } + return returnArray; + } + /**Returns map by internal name.*/ public Map getByName(String name){ return maps.get(name); } //TODO GWT support: read from prefs string if custom + /**Reads all tile data from a map. Should be used sparingly.*/ public MapTileData readTileData(Map map){ try { InputStream stream; @@ -56,6 +71,35 @@ public class Maps implements Disposable{ } } + /**Load all maps. Should be called at application start.*/ + public void load(){ + try { + for (String name : defaultMapNames) { + loadMap(Gdx.files.internal("maps/" + name + "." + mapExtension), false); + } + }catch (IOException e){ + throw new RuntimeException(e); + } + + for(FileHandle file : customMapDirectory.list()){ + try{ + loadMap(file, true); + }catch (IOException e){ + Log.err("Failed to load custom map file '{0}'!", file); + Log.err(e); + } + } + } + + private void loadMap(FileHandle file, boolean custom) throws IOException{ + DataInputStream ds = new DataInputStream(file.read()); + MapMeta meta = readMapMeta(ds); + Map map = new Map(file.nameWithoutExtension(), meta, custom); + + maps.put(map.name, map); + allMaps.add(map); + } + private MapTileData readTileData(DataInputStream stream) throws IOException{ MapMeta meta = readMapMeta(stream); byte[] bytes = new byte[stream.available()]; diff --git a/core/src/io/anuke/mindustry/world/WorldGenerator.java b/core/src/io/anuke/mindustry/world/WorldGenerator.java index 488679416a..c41ac84694 100644 --- a/core/src/io/anuke/mindustry/world/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/world/WorldGenerator.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.world; import com.badlogic.gdx.utils.ObjectMap; import io.anuke.mindustry.io.MapTileData; -import io.anuke.mindustry.io.MapTileData.TileData; +import io.anuke.mindustry.io.MapTileData.TileDataWriter; import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.ucore.noise.Noise; @@ -23,7 +23,7 @@ public class WorldGenerator { for(int x = 0; x < data.width(); x ++){ for(int y = 0; y < data.height(); y ++){ - TileData tile = data.read(); + TileDataWriter tile = data.read(); tiles[x][y] = new Tile(x, y, tile.floor, tile.wall, tile.rotation, tile.team); //TODO ores, plants, extra decoration? diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 7299696239..4256303087 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -8,6 +8,7 @@ import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.EventType.GameOverEvent; import io.anuke.mindustry.game.GameMode; +import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.Net; @@ -17,7 +18,6 @@ import io.anuke.mindustry.net.Packets.ChatPacket; import io.anuke.mindustry.net.Packets.KickReason; import io.anuke.mindustry.net.TraceInfo; import io.anuke.mindustry.ui.fragments.DebugFragment; -import io.anuke.mindustry.world.Map; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.*; import io.anuke.ucore.modules.Module; @@ -34,8 +34,6 @@ import java.util.Scanner; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.util.Log.*; -; - public class ServerControl extends Module { private final CommandHandler handler = new CommandHandler(""); private ShuffleMode mode; @@ -81,12 +79,12 @@ public class ServerControl extends Module { } if (mode != ShuffleMode.off) { - Array maps = mode == ShuffleMode.both ? world.maps().getAllMaps() : - mode == ShuffleMode.normal ? world.maps().getDefaultMaps() : world.maps().getCustomMaps(); + Array maps = mode == ShuffleMode.both ? world.maps().all() : + mode == ShuffleMode.normal ? world.maps().defaultMaps() : world.maps().customMaps(); Map previous = world.getMap(); Map map = previous; - while (map == previous || !map.visible) map = maps.random(); + while (map == previous) map = maps.random(); info("Selected next map to be {0}.", map.name); state.set(State.playing); @@ -134,7 +132,7 @@ public class ServerControl extends Module { String search = arg[0]; Map result = null; - for(Map map : world.maps().list()){ + for(Map map : world.maps().all()){ if(map.name.equalsIgnoreCase(search)) result = map; } @@ -165,8 +163,8 @@ public class ServerControl extends Module { handler.register("maps", "Display all available maps.", arg -> { Log.info("Maps:"); - for(Map map : world.maps().getAllMaps()){ - Log.info(" &ly{0}: &lb&fi{1} / {2}x{3}", map.name, map.custom ? "Custom" : "Default", map.getWidth(), map.getHeight()); + for(Map map : world.maps().all()){ + Log.info(" &ly{0}: &lb&fi{1} / {2}x{3}", map.name, map.custom ? "Custom" : "Default", map.meta.width, map.meta.height); } }); @@ -505,7 +503,8 @@ public class ServerControl extends Module { return; } - world.removeBlock(world.getCore()); + Events.fire(GameOverEvent.class); + info("Core destroyed."); }); diff --git a/server/src/io/anuke/mindustry/server/mapgen/ProcGen.java b/server/src/io/anuke/mindustry/server/mapgen/ProcGen.java index b50ceeafbb..8e374d24d9 100644 --- a/server/src/io/anuke/mindustry/server/mapgen/ProcGen.java +++ b/server/src/io/anuke/mindustry/server/mapgen/ProcGen.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.server.mapgen; -import io.anuke.mindustry.world.Map; +import io.anuke.mindustry.io.Map; import io.anuke.ucore.noise.RidgedPerlin; import io.anuke.ucore.noise.Simplex;