diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 2791a512c7..5b508cd1d8 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -16,6 +16,7 @@ screenshot.invalid = Map too large, potentially not enough memory for screenshot gameover = Game Over gameover.pvp = The[accent] {0}[] team is victorious! highscore = [accent]New highscore! +copied = Copied. load.sound = Sounds load.map = Maps @@ -24,10 +25,17 @@ load.content = Content load.system = System load.mod = Mods +schematic = Schematic schematic.add = Save Schematic... schematics = Schematics +schematic.exportfile = Export File +schematic.copy = Copy to Clipboard +schematic.shareworkshop = Share on Workshop schematic.flip = [accent][[{0}][]/[accent][[{1}][]: Flip Schematic schematic.saved = Schematic saved. +schematic.delete.confirm = This schematic will be utterly eradicated. +schematic.rename = Rename Schematic +schematic.info = {0}x{1}, {2} blocks stat.wave = Waves Defeated:[accent] {0} stat.enemiesDestroyed = Enemies Destroyed:[accent] {0} diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index bfedda548c..3428e928d5 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -36,6 +36,11 @@ public interface Platform{ return Array.with(); } + /** Steam: Return external workshop schematics to be loaded.*/ + default Array getExternalSchematics(){ + return Array.with(); + } + /** Steam: View a map listing on the workshop.*/ default void viewMapListing(Map map){} diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index da0182ee35..107971e414 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -305,7 +305,7 @@ public class UI implements ApplicationListener, Loadable{ } public void showTextInput(String title, String text, String def, Consumer confirmed){ - showTextInput(title, text, 24, def, confirmed); + showTextInput(title, text, 32, def, confirmed); } public void showTextInput(String titleText, String text, int textLength, String def, Consumer confirmed){ diff --git a/core/src/io/anuke/mindustry/game/Schematic.java b/core/src/io/anuke/mindustry/game/Schematic.java index 5d7b5565f4..9b7b8aabf1 100644 --- a/core/src/io/anuke/mindustry/game/Schematic.java +++ b/core/src/io/anuke/mindustry/game/Schematic.java @@ -1,15 +1,17 @@ package io.anuke.mindustry.game; import io.anuke.arc.collection.*; +import io.anuke.arc.collection.IntIntMap.*; import io.anuke.arc.files.*; import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; public class Schematic{ public final Array tiles; public StringMap tags; public int width, height; - public boolean workshop; public @Nullable FileHandle file; public Schematic(Array tiles, StringMap tags, int width, int height){ @@ -19,10 +21,30 @@ public class Schematic{ this.height = height; } + public Array requirements(){ + IntIntMap amounts = new IntIntMap(); + + tiles.each(t -> { + for(ItemStack stack : t.block.requirements){ + amounts.getAndIncrement(stack.item.id, 0, stack.amount); + } + }); + Array stacks = new Array<>(); + for(Entry ent : amounts.entries()){ + stacks.add(new ItemStack(Vars.content.item(ent.key), ent.value)); + } + stacks.sort(); + return stacks; + } + public String name(){ return tags.get("name", "unknown"); } + public boolean isWorkshop(){ + return tags.containsKey("workshop"); + } + public static class Stile{ public @NonNull Block block; public short x, y; diff --git a/core/src/io/anuke/mindustry/game/Schematics.java b/core/src/io/anuke/mindustry/game/Schematics.java index ea8fbe54cb..8d49a99ca0 100644 --- a/core/src/io/anuke/mindustry/game/Schematics.java +++ b/core/src/io/anuke/mindustry/game/Schematics.java @@ -53,21 +53,28 @@ public class Schematics implements Loadable{ /** Load all schematics in the folder immediately.*/ public void load(){ all.clear(); - for(FileHandle file : schematicDirectory.list()){ - if(!file.extension().equals(schematicExtension)) continue; - try{ - all.add(read(file)); - }catch(IOException e){ - e.printStackTrace(); - } + for(FileHandle file : schematicDirectory.list()){ + loadFile(file); } + platform.getExternalSchematics().each(this::loadFile); + Core.app.post(() -> { - shadowBuffer = new FrameBuffer(maxSchematicSize + padding, maxSchematicSize + padding); + shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 2, maxSchematicSize + padding + 2); }); } + private void loadFile(FileHandle file){ + if(!file.extension().equals(schematicExtension)) return; + + try{ + all.add(read(file)); + }catch(IOException e){ + Log.err(e); + } + } + public Array all(){ return all; } @@ -159,6 +166,13 @@ public class Schematics implements Loadable{ } } + public void remove(Schematic s){ + all.remove(s); + if(s.file != null){ + s.file.delete(); + } + } + /** Creates a schematic from a world selection. */ public Schematic create(int x, int y, int x2, int y2){ NormalizeResult result = PlaceUtils.normalizeArea(x, y, x2, y2, 0, false, maxSchematicSize); diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index f76a3e199a..d96fda1e1c 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -71,7 +71,7 @@ public class DesktopInput extends InputHandler{ lastSchematic.tags.put("name", text); schematics.add(lastSchematic); ui.showInfoFade("$schematic.saved"); - ui.schematics.showInfo(lastSchematic); + //ui.schematics.showInfo(lastSchematic); }); }).colspan(2).size(250f, 50f); }); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java index 8bb6d4a9f8..8d0afd5fd9 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java @@ -1,16 +1,20 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.arc.*; +import io.anuke.arc.collection.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.Texture.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.ui.*; +import io.anuke.arc.scene.ui.ImageButton.*; +import io.anuke.arc.scene.ui.TextButton.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.Schematics.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.ui.*; import static io.anuke.mindustry.Vars.*; @@ -39,7 +43,7 @@ public class SchematicsDialog extends FloatingDialog{ cont.table(s -> { s.left(); - s.addImage(Icon.zoom).color(Pal.gray); + s.addImage(Icon.zoom); s.addField(search, res -> { search = res; rebuildPane[0].run(); @@ -54,37 +58,64 @@ public class SchematicsDialog extends FloatingDialog{ rebuildPane[0] = () -> { t.clear(); int i = 0; + + if(!schematics.all().contains(s -> search.isEmpty() || s.name().contains(search))){ + t.add("$none"); + } + for(Schematic s : schematics.all()){ if(!search.isEmpty() && !s.name().contains(search)) continue; Button[] sel = {null}; sel[0] = t.addButton(b -> { b.top(); - b.margin(4f); + b.margin(0f); b.table(buttons -> { buttons.left(); buttons.defaults().size(50f); - buttons.addImageButton(Icon.pencilSmall, Styles.clearPartiali, () -> { + ImageButtonStyle style = Styles.clearPartiali; + + buttons.addImageButton(Icon.infoSmall, style, () -> { + showInfo(s); }); - buttons.addImageButton(Icon.trash16Small, Styles.clearPartiali, () -> { + //if(s.isWorkshop()){ + buttons.addImageButton(Icon.loadMapSmall, style, () -> { + showExport(s); + }); + //} + buttons.addImageButton(Icon.pencilSmall, style, () -> { + ui.showTextInput("$schematic.rename", "$name", s.name(), res -> { + s.tags.put("name", res); + }); + }); + + buttons.addImageButton(Icon.trash16Small, style, () -> { + ui.showConfirm("$confirm", "$schematic.delete.confirm", () -> { + schematics.remove(s); + rebuildPane[0].run(); + }); }); }).growX().height(50f); b.row(); - b.add(s.name()).center().color(Color.lightGray).top().left().get().setEllipsis(true); - b.row(); - b.add(new SchematicImage(s).setScaling(Scaling.fit).setName("border")).size(200f); + b.stack(new SchematicImage(s).setScaling(Scaling.fit), new Table(n -> { + n.top(); + n.table(Styles.black3, c -> { + Label label = c.add(s.name()).style(Styles.outlineLabel).color(Color.white).top().growX().get(); + label.setEllipsis(true); + label.setAlignment(Align.center); + }).growX().margin(1).pad(4).padBottom(0); + })).size(200f); }, () -> { if(sel[0].childrenPressed()) return; control.input.useSchematic(s); hide(); }).pad(4).style(Styles.cleari).get(); - //BorderImage image = sel[0].find("border"); - //image.update(() -> image.borderColor = (sel[0].isOver() ? Pal.accent : Pal.gray)); + sel[0].getStyle().up = Tex.pane; if(++i % 4 == 0){ t.row(); @@ -100,6 +131,38 @@ public class SchematicsDialog extends FloatingDialog{ info.show(schematic); } + public void showExport(Schematic s){ + FloatingDialog dialog = new FloatingDialog("$editor.export"); + dialog.cont.pane(p -> { + p.margin(10f); + p.table(Tex.button, t -> { + TextButtonStyle style = Styles.cleart; + t.defaults().size(280f, 60f).left(); + t.addImageTextButton("$schematic.shareworkshop", Icon.wikiSmall, style, () -> { + + }).marginLeft(12f); + t.row(); + t.addImageTextButton("$schematic.copy", Icon.copySmall, style, () -> { + dialog.hide(); + ui.showInfoFade("$copied"); + Core.app.setClipboardText(schematics.writeBase64(s)); + }).marginLeft(12f); + t.row(); + t.addImageTextButton("$schematic.exportfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(false, schematicExtension, file -> { + dialog.hide(); + try{ + Schematics.write(s, file); + }catch(Exception e){ + ui.showException(e); + } + })).marginLeft(12f); + }); + }); + + dialog.addCloseButton(); + dialog.show(); + } + public static class SchematicImage extends Image{ public float scaling = 16f; public float thickness = 4f; @@ -107,21 +170,27 @@ public class SchematicsDialog extends FloatingDialog{ public SchematicImage(Schematic s){ super(schematics.getPreview(s, PreviewRes.high)); + setScaling(Scaling.fit); } @Override public void draw(){ + boolean checked = getParent().getParent() instanceof Button + && ((Button)getParent().getParent()).isOver(); + Texture background = Core.assets.get("sprites/schematic-background.png", Texture.class); TextureRegion region = Draw.wrap(background); float xr = width / scaling; float yr = height / scaling; region.setU2(xr); region.setV2(yr); + Draw.color(); + Draw.alpha(parentAlpha); Draw.rect(region, x + width/2f, y + height/2f, width, height); super.draw(); - Draw.color(borderColor); + Draw.color(checked ? Pal.accent : borderColor); Draw.alpha(parentAlpha); Lines.stroke(Scl.scl(thickness)); Lines.rect(x, y, width, height); @@ -133,16 +202,31 @@ public class SchematicsDialog extends FloatingDialog{ SchematicInfoDialog(){ super(""); - setFillParent(false); + setFillParent(true); addCloseButton(); } public void show(Schematic schem){ cont.clear(); - title.setText(schem.name()); + title.setText("[[" + Core.bundle.get("schematic") + "] " +schem.name()); - cont.add(new BorderImage(schematics.getPreview(schem, PreviewRes.high))).maxSize(400f); + cont.add(Core.bundle.format("schematic.info", schem.width, schem.height, schem.tiles.size)).color(Color.lightGray); cont.row(); + cont.add(new SchematicImage(schem)).maxSize(800f); + cont.row(); + + Array arr = schem.requirements(); + cont.table(r -> { + int i = 0; + for(ItemStack s : arr){ + r.addImage(s.item.icon(Cicon.small)).left(); + r.add(s.amount + "").padLeft(2).left().color(Color.lightGray).padRight(4); + + if(++i % 4 == 0){ + r.row(); + } + } + }); show(); } diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 7e5e915171..11bc63a8db 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -118,7 +118,7 @@ public class Block extends BlockStorage{ public float idleSoundVolume = 0.5f; /** Cost of constructing this block. */ - public ItemStack[] requirements = new ItemStack[]{}; + public ItemStack[] requirements = {}; /** Category in place menu. */ public Category category = Category.distribution; /** Cost of building this block; do not modify directly! */ diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index e97099f3ef..f6e8758342 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -258,6 +258,12 @@ public class DesktopLauncher extends ClientLauncher{ return !steam ? super.getExternalMods() : SVars.workshop.getModFiles(); } + @Override + public Array getExternalSchematics(){ + return !steam ? super.getExternalMods() : SVars.workshop.getSchematicFiles(); + } + + @Override public void viewMapListing(Map map){ viewListing(map.file.parent().name()); diff --git a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java index 31f6029e44..76f802911d 100644 --- a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java +++ b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java @@ -23,6 +23,7 @@ public class SWorkshop implements SteamUGCCallback{ private Map lastMap; private Array mapFiles; private Array modFiles; + private Array schematicFiles; private ObjectMap, SteamResult>> detailHandlers = new ObjectMap<>(); public SWorkshop(){ @@ -37,6 +38,7 @@ public class SWorkshop implements SteamUGCCallback{ }).select(f -> f != null && f.list().length > 0); mapFiles = folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(mapExtension)).map(f -> f.list()[0]); + schematicFiles = folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(schematicExtension)).map(f -> f.list()[0]); modFiles = folders.select(f -> f.child("mod.json").exists()); if(!mapFiles.isEmpty()){ @@ -55,6 +57,11 @@ public class SWorkshop implements SteamUGCCallback{ return modFiles; } + + public Array getSchematicFiles(){ + return schematicFiles; + } + public void publishMap(Map map){ if(map.tags.containsKey("steamid")){ Log.info("Map already published, redirecting to ID."); diff --git a/gradle.properties b/gradle.properties index e16cd8686d..c07c359326 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=85f95089a6521880c87e0fc011b4a09f207d360d +archash=e82f446a81abba5d6f712b9053c1b84ec9a73156