diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index d9825ceeff..e719fef6d4 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -11,13 +11,14 @@ text.link.google-play.description=Google Play store listing text.link.wiki.description=official Mindustry wiki text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. text.gameover=The core was destroyed. text.highscore=[YELLOW]New highscore! text.lasted=You lasted until wave text.level.highscore=High Score: [accent]{0} text.level.delete.title=Confirm Delete -text.level.delete=Are you sure you want to delete\nthe map "[orange]{0}"? +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? text.level.select=Level Select text.level.mode=Gamemode: text.savegame=Save Game @@ -26,6 +27,7 @@ text.joingame=Join Game text.addplayers=Add/Remove Players text.newgame=New Game text.quit=Quit +text.maps=Maps text.about.button=About text.name=Name: text.public=Public @@ -151,15 +153,20 @@ text.enemies.single={0} Enemy text.loadimage=Load Image text.saveimage=Save Image text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.editor.openin=Open In Editor text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: text.editor.mapinfo=Map Info text.editor.author=Author: text.editor.description=Description: text.editor.name=Name: text.editor.teams=Teams text.editor.badsize=[orange]Invalid image dimensions![]\nValid map dimensions: {0} -text.editor.errorimageload=Error loading image file:\n[orange]{0} -text.editor.errorimagesave=Error saving image file:\n[orange]{0} +text.editor.errorimageload=Error loading file:\n[orange]{0} +text.editor.errorimagesave=Error saving file:\n[orange]{0} text.editor.generate=Generate text.editor.resize=Resize text.editor.loadmap=Load Map diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index bb802382d7..f70d86a16f 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -9,7 +9,6 @@ import io.anuke.mindustry.Vars; import io.anuke.mindustry.editor.MapEditorDialog; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.input.InputHandler; -import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.fragments.*; import io.anuke.ucore.core.Core; @@ -42,6 +41,7 @@ public class UI extends SceneModule{ public AboutDialog about; public RestartDialog restart; public LevelDialog levels; + public MapsDialog maps; public LoadDialog load; public DiscordDialog discord; public JoinDialog join; @@ -172,6 +172,7 @@ public class UI extends SceneModule{ bans = new BansDialog(); admins = new AdminsDialog(); traces = new TraceDialog(); + maps = new MapsDialog(); localplayers = new LocalPlayerDialog(); build.begin(scene); @@ -251,21 +252,22 @@ public class UI extends SceneModule{ public void showInfo(String info){ new Dialog("$text.info.title", "dialog"){{ - content().margin(15).add(info).width(600f).get().setWrap(true); + getCell(content()).growX(); + content().margin(15).add(info).width(400f).wrap(); buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); }}.show(); } public void showError(String text){ new Dialog("$text.error.title", "dialog"){{ - content().margin(15).add(text); + content().margin(15).add(text).width(400f).wrap(); buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); }}.show(); } public void showConfirm(String title, String text, Listenable confirmed){ FloatingDialog dialog = new FloatingDialog(title); - dialog.content().add(text).pad(4f); + dialog.content().add(text).width(400f).wrap().pad(4f); dialog.buttons().defaults().size(200f, 54f).pad(2f); dialog.buttons().addButton("$text.cancel", dialog::hide); dialog.buttons().addButton("$text.ok", () -> { diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 6194a83ccb..e3a0f4611d 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -39,6 +39,7 @@ import io.anuke.ucore.util.Strings; import java.io.DataInputStream; import java.io.IOException; +import java.io.InputStream; import static io.anuke.mindustry.Vars.*; @@ -47,19 +48,17 @@ public class MapEditorDialog extends Dialog implements Disposable{ private MapView view; private MapInfoDialog infoDialog; private MapLoadDialog loadDialog; - private MapSaveDialog saveDialog; private MapResizeDialog resizeDialog; private ScrollPane pane; private FloatingDialog menu; private FileChooser openFile, saveFile, openImage, saveImage; - private ObjectMap tags = new ObjectMap<>(); private boolean saved = false; + private boolean shownWithMap = false; private ButtonGroup blockgroup; public MapEditorDialog(){ super("$text.mapeditor", "dialog"); - if(gwt) return; editor = new MapEditor(); view = new MapView(editor); @@ -73,7 +72,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ try{ if(!editor.getTags().containsKey("name")){ - tags.put("name", result.nameWithoutExtension()); + editor.getTags().put("name", result.nameWithoutExtension()); } MapIO.writeMap(result.write(false), editor.getTags(), editor.getMap()); }catch (Exception e){ @@ -118,7 +117,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ try{ MapTileData data = MapIO.readPixmap(new Pixmap(file)); - editor.beginEdit(data, new ObjectMap<>()); + editor.beginEdit(data, editor.getTags()); view.clearStack(); }catch (Exception e){ ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); @@ -136,23 +135,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ menu.content().table(t -> { t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5); - t.addImageTextButton("$text.editor.savemap", "icon-floppy-16", isize, () -> { - String name = editor.getTags().get("name", ""); - - if(name.isEmpty()){ - ui.showError("$text.editor.save.noname"); - }else{ - Map map = world.maps().getByName(name); - if(map != null && !map.custom){ - ui.showError("$text.editor.save.overwrite"); - }else{ - world.maps().saveAndReload(name, editor.getMap(), editor.getTags()); - ui.showInfoFade("$text.editor.saved"); - } - } - - menu.hide(); - }).size(swidth*2f + 10, 60f).colspan(2); + t.addImageTextButton("$text.editor.savemap", "icon-floppy-16", isize, this::save).size(swidth*2f + 10, 60f).colspan(2); t.row(); @@ -172,11 +155,23 @@ public class MapEditorDialog extends Dialog implements Disposable{ createDialog("$text.editor.import", "$text.editor.importmap", "$text.editor.importmap.description", "icon-load-map", (Listenable)loadDialog::show, "$text.editor.importfile", "$text.editor.importfile.description", "icon-file", (Listenable)openFile::show, - "$text.editor.importimage", "$text.editor.importimage.description", "icon-file-image", (Listenable)openImage::show)); + "$text.editor.importimage", "$text.editor.importimage.description", "icon-file-image", (Listenable)() -> { + if(gwt){ + ui.showError("text.web.unsupported"); + }else { + openImage.show(); + } + })); t.addImageTextButton("$text.editor.export", "icon-save-map", isize, () -> createDialog("$text.editor.export", "$text.editor.exportfile", "$text.editor.exportfile.description", "icon-file", (Listenable)saveFile::show, - "$text.editor.exportimage", "$text.editor.exportimage.description", "icon-file-image", (Listenable)saveImage::show)); + "$text.editor.exportimage", "$text.editor.exportimage.description", "icon-file-image", (Listenable)() -> { + if(gwt){ + ui.showError("text.web.unsupported"); + }else { + saveImage.show(); + } + })); t.row(); @@ -204,7 +199,6 @@ public class MapEditorDialog extends Dialog implements Disposable{ }); loadDialog = new MapLoadDialog(map -> { - saveDialog.setFieldText(map.name); ui.loadAnd(() -> { try (DataInputStream stream = new DataInputStream(map.stream.get())){ @@ -219,15 +213,6 @@ public class MapEditorDialog extends Dialog implements Disposable{ } }); }); - - - saveDialog = new MapSaveDialog(name -> { - ui.loadAnd(() -> { - saved = true; - world.maps().saveAndReload(editor.getTags().get("name", name), editor.getMap(), editor.getTags()); - loadDialog.rebuild(); - }); - }); setFillParent(true); @@ -250,10 +235,12 @@ public class MapEditorDialog extends Dialog implements Disposable{ shown(() -> { saved = true; - editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>()); - blockgroup.getButtons().get(2).setChecked(true); - Core.scene.setScrollFocus(view); view.clearStack(); + Core.scene.setScrollFocus(view); + if(!shownWithMap){ + editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>()); + } + shownWithMap = false; Timers.runTask(10f, Platform.instance::updateRPC); }); @@ -261,12 +248,30 @@ public class MapEditorDialog extends Dialog implements Disposable{ hidden(() -> Platform.instance.updateRPC()); } + private void save(){ + String name = editor.getTags().get("name", ""); + + if(name.isEmpty()){ + ui.showError("$text.editor.save.noname"); + }else{ + Map map = world.maps().getByName(name); + if(map != null && !map.custom){ + ui.showError("$text.editor.save.overwrite"); + }else{ + world.maps().saveMap(name, editor.getMap(), editor.getTags()); + ui.showInfoFade("$text.editor.saved"); + } + } + + menu.hide(); + saved = true; + } + /**Argument format: * 0) button name * 1) description * 2) icon name - * 3) listener - */ + * 3) listener */ private FloatingDialog createDialog(String title, Object... arguments){ FloatingDialog dialog = new FloatingDialog(title); @@ -318,6 +323,22 @@ public class MapEditorDialog extends Dialog implements Disposable{ editor.renderer().dispose(); } + public void beginEditMap(InputStream is){ + ui.loadAnd(() -> { + try { + shownWithMap = true; + DataInputStream stream = new DataInputStream(is); + MapMeta meta = MapIO.readMapMeta(stream); + editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags); + is.close(); + show(); + }catch (Exception e){ + Log.err(e); + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + } + }); + } + public MapView getView() { return view; } @@ -482,7 +503,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ } if(Inputs.keyTap(Input.S)){ - saveDialog.save(); + save(); } if(Inputs.keyTap(Input.G)){ diff --git a/core/src/io/anuke/mindustry/editor/MapInfoDialog.java b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java index 30ce569782..2fc2427399 100644 --- a/core/src/io/anuke/mindustry/editor/MapInfoDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java @@ -67,6 +67,10 @@ public class MapInfoDialog extends FloatingDialog{ tags.put("oregen", enabled ? "1" : "0"); }).left(); + name.change(); + description.change(); + author.change(); + Platform.instance.addDialog(name, 50); Platform.instance.addDialog(author, 50); Platform.instance.addDialog(description, 1000); diff --git a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java index 5bee3fec69..eafa4386e3 100644 --- a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java @@ -36,6 +36,7 @@ public class MapLoadDialog extends FloatingDialog{ public void rebuild(){ content().clear(); + selected = world.maps().all().first(); ButtonGroup group = new ButtonGroup<>(); @@ -52,7 +53,7 @@ public class MapLoadDialog extends FloatingDialog{ for (Map map : world.maps().all()) { - TextButton button = new TextButton(map.meta.name(), "toggle"); + TextButton button = new TextButton(map.getDisplayName(), "toggle"); button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); button.getCells().reverse(); button.clicked(() -> selected = map); diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index 4a57c92f2c..a12554dec2 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -198,6 +198,9 @@ public class MapRenderer implements Disposable{ @Override public void dispose() { + if(chunks == null){ + return; + } for(int x = 0; x < chunks.length; x ++){ for(int y = 0; y < chunks[0].length; y ++){ if(chunks[x][y] != null){ diff --git a/core/src/io/anuke/mindustry/io/Map.java b/core/src/io/anuke/mindustry/io/Map.java index 4182bf1916..6f4c36c929 100644 --- a/core/src/io/anuke/mindustry/io/Map.java +++ b/core/src/io/anuke/mindustry/io/Map.java @@ -23,4 +23,8 @@ public class Map { this.meta = meta; this.stream = streamSupplier; } + + public String getDisplayName(){ + return meta.tags.get("name", name); + } } diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index 0f315a50c1..3d2f29e8dc 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -75,7 +75,8 @@ public class Maps implements Disposable{ loadCustomMaps(); } - public void saveAndReload(String name, MapTileData data, ObjectMap tags){ + /**Save a map. This updates all values and stored data necessary.*/ + public void saveMap(String name, MapTileData data, ObjectMap tags){ try { if (!gwt) { FileHandle file = customMapDirectory.child(name + "." + mapExtension); @@ -92,18 +93,43 @@ public class Maps implements Disposable{ } if(maps.containsKey(name)){ - maps.get(name).texture.dispose(); + if(maps.get(name).texture != null) { + maps.get(name).texture.dispose(); + maps.get(name).texture = null; + } + allMaps.removeValue(maps.get(name), true); } Map map = new Map(name, new MapMeta(version, tags, data.width(), data.height(), null), true, getStreamFor(name)); if (!headless){ map.texture = new Texture(MapIO.generatePixmap(data)); } + allMaps.add(map); + maps.put(name, map); }catch (IOException e){ throw new RuntimeException(e); } - //todo implement + } + + /**Removes a map completely.*/ + public void removeMap(Map map){ + if(map.texture != null){ + map.texture.dispose(); + map.texture = null; + } + + maps.remove(map.name); + allMaps.removeValue(map, true); + + if (!gwt) { + customMapDirectory.child(map.name + "." + mapExtension).delete(); + } else { + customMapNames.removeValue(map.name, false); + Settings.putString("map-data-" + map.name, ""); + Settings.putString("custom-maps", json.toJson(customMapNames)); + Settings.save(); + } } private void loadMap(String name, Supplier supplier, boolean custom) throws IOException{ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java index a2474e892c..4e4d3f4353 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java @@ -19,9 +19,11 @@ import io.anuke.ucore.scene.ui.layout.Unit; import java.util.Arrays; +import static io.anuke.mindustry.Vars.gwt; + public class FileChooser extends FloatingDialog { private Table files; - private FileHandle homeDirectory = Gdx.files.absolute(Gdx.files.getExternalStoragePath()); + private FileHandle homeDirectory = gwt ? Gdx.files.internal("") : Gdx.files.absolute(Gdx.files.getExternalStoragePath()); private FileHandle directory = homeDirectory; private ScrollPane pane; private TextField navigation, filefield; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java index 7a9b134d76..a4a9ebdb6f 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java @@ -17,12 +17,9 @@ import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; public class LevelDialog extends FloatingDialog{ - private ScrollPane pane; public LevelDialog(){ super("$text.level.select"); - getTitleTable().getCell(title()).growX().center(); - getTitleTable().center(); addCloseButton(); shown(this::setup); } @@ -31,7 +28,7 @@ public class LevelDialog extends FloatingDialog{ content().clear(); Table maps = new Table(); - pane = new ScrollPane(maps); + ScrollPane pane = new ScrollPane(maps); pane.setFadeScrollBars(false); int maxwidth = 4; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java new file mode 100644 index 0000000000..837e2122c4 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java @@ -0,0 +1,116 @@ +package io.anuke.mindustry.ui.dialogs; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Scaling; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.ui.BorderImage; +import io.anuke.ucore.scene.event.Touchable; +import io.anuke.ucore.scene.ui.Image; +import io.anuke.ucore.scene.ui.ScrollPane; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; + +import static io.anuke.mindustry.Vars.ui; +import static io.anuke.mindustry.Vars.world; + +public class MapsDialog extends FloatingDialog { + + public MapsDialog() { + super("$text.maps"); + addCloseButton(); + shown(this::setup); + } + + void setup(){ + content().clear(); + + Table maps = new Table(); + maps.marginRight(24); + + ScrollPane pane = new ScrollPane(maps, "clear-black"); + pane.setFadeScrollBars(false); + + int maxwidth = 4; + float mapsize = 200f; + + int i = 0; + for(Map map : world.maps().all()){ + + if(i % maxwidth == 0){ + maps.row(); + } + + TextButton button = maps.addButton("", "clear", () -> showMapInfo(map)).width(mapsize).pad(8).get(); + button.clearChildren(); + button.margin(6); + button.add(map.meta.tags.get("name", map.name)).growX().center().get().setEllipsis(true); + button.row(); + button.addImage("white").growX().pad(4).color(Color.GRAY); + button.row(); + ((Image)button.stack(new Image(map.texture), new BorderImage(map.texture)).size(mapsize-20f).get().getChildren().first()).setScaling(Scaling.fit); + button.row(); + button.add(map.custom ? "$text.custom" : "$text.builtin").color(Color.GRAY).padTop(3); + + i ++; + } + + content().add(pane).uniformX(); + } + + void showMapInfo(Map map){ + FloatingDialog dialog = new FloatingDialog("$text.editor.mapinfo"); + dialog.addCloseButton(); + + float mapsize = 300f; + Table table = dialog.content(); + + ((Image) table.stack(new Image(map.texture), new BorderImage(map.texture)).size(mapsize).get().getChildren().first()).setScaling(Scaling.fit); + + table.table("clear", desc -> { + desc.top(); + Table t = new Table(); + + ScrollPane pane = new ScrollPane(t, "clear-black"); + desc.add(pane).grow(); + + t.top(); + t.defaults().padTop(10).left(); + + t.add("$text.editor.name").padRight(10).color(Color.GRAY).padTop(0); + t.row(); + t.add(map.meta.tags.get("name", map.name)).growX().wrap().padTop(2); + t.row(); + t.add("$text.editor.author").padRight(10).color(Color.GRAY); + t.row(); + t.add(map.meta.author()).growX().wrap().padTop(2); + t.row(); + t.add("$text.editor.description").padRight(10).color(Color.GRAY).top(); + t.row(); + t.add(map.meta.description()).growX().wrap().padTop(2); + t.row(); + t.add("$text.editor.oregen.info").padRight(10).color(Color.GRAY); + t.row(); + t.add(map.meta.hasOreGen() ? "$text.on" : "$text.off").padTop(2); + }).height(mapsize).width(mapsize).margin(6); + + table.row(); + + table.addImageTextButton("$text.editor.openin", "icon-load-map", "clear", 16*2, () -> { + Vars.ui.editor.beginEditMap(map.stream.get()); + dialog.hide(); + hide(); + }).fillX().height(50f).marginLeft(6); + + table.addImageTextButton("$text.delete", "icon-trash-16", "clear", 16*2, () -> { + ui.showConfirm("$text.confirm", Bundles.format("text.map.delete", map.name), () -> { + world.maps().removeMap(map); + dialog.hide(); + setup(); + }); + }).fillX().height(50f).marginLeft(6).disabled(!map.custom).touchable(map.custom ? Touchable.enabled : Touchable.disabled); + + dialog.show(); + } +} diff --git a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java index 6a9cff5836..81ab6a12d8 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java @@ -31,21 +31,15 @@ public class MenuFragment implements Fragment{ row(); - add(new MenuButton("icon-editor", "$text.editor", () -> { - if(gwt){ - ui.showInfo("$text.editor.web"); - }else{ - ui.editor.show(); - } - })); - - add(new MenuButton("icon-tools", "$text.settings", ui.settings::show)); + add(new MenuButton("icon-editor", "$text.editor", ui.editor::show)); + + add(new MenuButton("icon-menu", "$text.maps", ui.maps::show)); row(); add(new MenuButton("icon-info", "$text.about.button", ui.about::show)); - add(new MenuButton("icon-menu", "$text.changelog.title", ui.changelog::show)); + add(new MenuButton("icon-tools", "$text.settings", ui.settings::show)); row(); diff --git a/ios/src/io/anuke/mindustry/IOSLauncher.java b/ios/src/io/anuke/mindustry/IOSLauncher.java index 00f0d6b6f4..6d9db11b85 100644 --- a/ios/src/io/anuke/mindustry/IOSLauncher.java +++ b/ios/src/io/anuke/mindustry/IOSLauncher.java @@ -137,7 +137,8 @@ public class IOSLauncher extends IOSApplication.Delegate { if(!ui.editor.isShown()){ ui.editor.show(); } - ui.editor.tryLoadMap(file); + + ui.editor.beginEditMap(file.read()); } }); }