diff --git a/core/assets-raw/sprites/blocks/storage/unloader-center.png b/core/assets-raw/sprites/blocks/storage/unloader-center.png new file mode 100644 index 0000000000..e5a2fffd15 Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/unloader-center.png differ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 258828f879..ffe37ca6a1 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,6 +25,22 @@ load.content = Content load.system = System load.mod = Mods +schematic = Schematic +schematic.add = Save Schematic... +schematics = Schematics +schematic.import = Import Schematic... +schematic.exportfile = Export File +schematic.importfile = Import File +schematic.browseworkshop = Browse Workshop +schematic.copy = Copy to Clipboard +schematic.copy.import = Import from 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} stat.built = Buildings Built:[accent] {0} @@ -83,6 +100,8 @@ mod.import.github = Import Github Mod mod.remove.confirm = This mod will be deleted. mod.author = [LIGHT_GRAY]Author:[] {0} mod.missing = This save contains mods that you have recently updated or no longer have installed. Save corruption may occur. Are you sure you want to load it?\n[lightgray]Mods:\n{0} +mod.preview.missing = Before publishing this mod in the workshop, you must add an image preview.\nPlace an image named[accent] preview.png[] into the mod's folder and try again. +mod.folder.missing = Only mods in folder form can be published on the workshop.\nTo convert any mod into a folder, simply unzip its file into a folder and delete the old zip, then restart your game or reload your mods. about.button = About name = Name: @@ -218,6 +237,7 @@ loading = [accent]Loading... reloading = [accent]Reloading Mods... saving = [accent]Saving... cancelbuilding = [accent][[{0}][] to clear plan +selectschematic = [accent][[{0}][] to select+copy pausebuilding = [accent][[{0}][] to pause building resumebuilding = [scarlet][[{0}][] to resume building wave = [accent]Wave {0} @@ -238,16 +258,17 @@ map.nospawn = This map does not have any cores for the player to spawn in! Add a map.nospawn.pvp = This map does not have any enemy cores for player to spawn into! Add[SCARLET] non-orange[] cores to this map in the editor. map.nospawn.attack = This map does not have any enemy cores for player to attack! Add[SCARLET] red[] cores to this map in the editor. map.invalid = Error loading map: corrupted or invalid map file. -map.publish.error = Error publishing map: {0} -map.update = Update Map -map.load.error = Error fetching workshop details: {0} -map.missing = This map has been deleted or moved.\n[lightgray]The workshop listing has now been automatically un-linked from the map. +workshop.update = Update Item +workshop.error = Error fetching workshop details: {0} map.publish.confirm = Are you sure you want to publish this map?\n\n[lightgray]Make sure you agree to the Workshop EULA first, or your maps will not show up! map.menu = Select what you would like to do with this map. -map.changelog = Changelog (optional): +changelog = Changelog (optional): eula = Steam EULA -map.publish = Map published. -map.publishing = [accent]Publishing map... +missing = This item has been deleted or moved.\n[lightgray]The workshop listing has now been automatically un-linked. +publishing = [accent]Publishing... +publish.confirm = Are you sure you want to publish this?\n\n[lightgray]Make sure you agree to the Workshop EULA first, or your items will not show up! +publish.error = Error publishing item: {0} + editor.brush = Brush editor.openin = Open In Editor editor.oregen = Ore Generation @@ -627,6 +648,10 @@ keybind.press.axis = Press an axis or key... keybind.screenshot.name = Map Screenshot keybind.move_x.name = Move x keybind.move_y.name = Move y +keybind.schematic_select.name = Select Region +keybind.schematic_menu.name = Schematic Menu +keybind.schematic_flip_x.name = Flip Schematic X +keybind.schematic_flip_y.name = Flip Schematic Y keybind.fullscreen.name = Toggle Fullscreen keybind.select.name = Select/Shoot keybind.diagonal_placement.name = Diagonal Placement diff --git a/core/assets/sprites/schematic-background.png b/core/assets/sprites/schematic-background.png new file mode 100644 index 0000000000..d0085b65d9 Binary files /dev/null and b/core/assets/sprites/schematic-background.png differ diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index c65d7952b6..fb1de5ace9 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -1348,6 +1348,13 @@ spore-press-top orig: 64, 64 offset: 0, 0 index: -1 +unloader-center + rotate: false + xy: 1735, 855 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 arc-heat rotate: false xy: 609, 1262 @@ -1770,14 +1777,14 @@ scale_marker index: -1 scorch1 rotate: false - xy: 1735, 787 + xy: 1735, 753 size: 28, 100 orig: 28, 100 offset: 0, 0 index: -1 scorch2 rotate: false - xy: 1765, 787 + xy: 1765, 753 size: 28, 100 orig: 28, 100 offset: 0, 0 @@ -4332,7 +4339,7 @@ item-copper-small index: -1 item-copper-tiny rotate: false - xy: 1735, 769 + xy: 1017, 691 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -4367,7 +4374,7 @@ item-graphite-small index: -1 item-graphite-tiny rotate: false - xy: 1017, 691 + xy: 1533, 777 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -4402,7 +4409,7 @@ item-lead-small index: -1 item-lead-tiny rotate: false - xy: 1533, 777 + xy: 1769, 879 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -4752,7 +4759,7 @@ item-thorium-small index: -1 item-thorium-tiny rotate: false - xy: 1753, 769 + xy: 1035, 683 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -4787,7 +4794,7 @@ item-titanium-small index: -1 item-titanium-tiny rotate: false - xy: 1035, 683 + xy: 1769, 861 size: 16, 16 orig: 16, 16 offset: 0, 0 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 7fc5be261e..6fad68943b 100644 Binary files a/core/assets/sprites/sprites.png and b/core/assets/sprites/sprites.png differ diff --git a/core/src/io/anuke/mindustry/ClientLauncher.java b/core/src/io/anuke/mindustry/ClientLauncher.java index 3234b1f9c5..e337ac1602 100644 --- a/core/src/io/anuke/mindustry/ClientLauncher.java +++ b/core/src/io/anuke/mindustry/ClientLauncher.java @@ -9,6 +9,7 @@ import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.async.*; import io.anuke.mindustry.core.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.EventType.*; @@ -81,6 +82,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform add(netClient = new NetClient()); assets.load(mods); + assets.load(schematics); assets.loadRun("contentinit", ContentLoader.class, () -> { content.init(); @@ -118,10 +120,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform for(ApplicationListener listener : modules){ listener.init(); } - super.resize(graphics.getWidth(), graphics.getHeight()); mods.each(Mod::init); finished = true; Events.fire(new ClientLoadEvent()); + super.resize(graphics.getWidth(), graphics.getHeight()); + app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight()))))); } }else{ super.update(); @@ -133,11 +136,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform long target = (1000 * 1000000) / targetfps; //target in nanos long elapsed = Time.timeSinceNanos(lastTime); if(elapsed < target){ - try{ - Thread.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000)); - }catch(InterruptedException ignored){ - //ignore - } + Threads.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000)); } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 2d8f4f83c0..d5c4a980f5 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -33,6 +33,10 @@ public class Vars implements Loadable{ public static boolean loadLocales = true; /** Maximum number of broken blocks. TODO implement or remove.*/ public static final int maxBrokenBlocks = 256; + /** Maximum schematic size.*/ + public static final int maxSchematicSize = 32; + /** All schematic base64 starts with this string.*/ + public static final String schematicBaseStart ="bXNjaAB"; /** IO buffer size. */ public static final int bufferSize = 8192; /** global charset, since Android doesn't support the Charsets class */ @@ -128,10 +132,14 @@ public class Vars implements Loadable{ public static FileHandle saveDirectory; /** data subdirectory used for mods */ public static FileHandle modDirectory; + /** data subdirectory used for schematics */ + public static FileHandle schematicDirectory; /** map file extension */ public static final String mapExtension = "msav"; /** save file extension */ public static final String saveExtension = "msav"; + /** schematic file extension */ + public static final String schematicExtension = "msch"; /** list of all locales that can be switched to */ public static Locale[] locales; @@ -146,6 +154,7 @@ public class Vars implements Loadable{ public static LoopControl loops; public static Platform platform = new Platform(){}; public static Mods mods; + public static Schematics schematics = new Schematics(); public static World world; public static Maps maps; @@ -251,6 +260,7 @@ public class Vars implements Loadable{ saveDirectory = dataDirectory.child("saves/"); tmpDirectory = dataDirectory.child("tmp/"); modDirectory = dataDirectory.child("mods/"); + schematicDirectory = dataDirectory.child("schematics/"); modDirectory.mkdirs(); diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index 1bcb6c6d59..824fd11251 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -1344,7 +1344,8 @@ public class Blocks implements ContentList{ Items.pyratite, Bullets.pyraFlame ); recoil = 0f; - reload = 4f; + reload = 5f; + coolantMultiplier = 2f; range = 60f; shootCone = 50f; targetAir = false; diff --git a/core/src/io/anuke/mindustry/content/Bullets.java b/core/src/io/anuke/mindustry/content/Bullets.java index 2fe1427343..31afc7e5b1 100644 --- a/core/src/io/anuke/mindustry/content/Bullets.java +++ b/core/src/io/anuke/mindustry/content/Bullets.java @@ -99,8 +99,7 @@ public class Bullets implements ContentList{ collidesTiles = false; splashDamageRadius = 25f; splashDamage = 30f; - incendAmount = 4; - incendSpread = 11f; + status = StatusEffects.burning; frontColor = Pal.lightishOrange; backColor = Pal.lightOrange; trailEffect = Fx.incendTrail; @@ -228,8 +227,7 @@ public class Bullets implements ContentList{ splashDamage = 10f; lifetime = 160f; hitEffect = Fx.blastExplosion; - incendSpread = 10f; - incendAmount = 3; + status = StatusEffects.burning; }}; missileSurge = new MissileBulletType(4.4f, 15, "bullet"){{ @@ -342,9 +340,7 @@ public class Bullets implements ContentList{ bulletHeight = 12f; frontColor = Pal.lightishOrange; backColor = Pal.lightOrange; - incendSpread = 3f; - incendAmount = 1; - incendChance = 0.3f; + status = StatusEffects.burning; inaccuracy = 3f; lifetime = 60f; }}; @@ -354,9 +350,7 @@ public class Bullets implements ContentList{ bulletHeight = 12f; frontColor = Color.valueOf("feb380"); backColor = Color.valueOf("ea8878"); - incendSpread = 3f; - incendAmount = 1; - incendChance = 0.3f; + status = StatusEffects.burning; lifetime = 60f; }}; @@ -385,9 +379,7 @@ public class Bullets implements ContentList{ bulletHeight = 21f; frontColor = Pal.lightishOrange; backColor = Pal.lightOrange; - incendSpread = 3f; - incendAmount = 2; - incendChance = 0.3f; + status = StatusEffects.burning; shootEffect = Fx.shootBig; }}; diff --git a/core/src/io/anuke/mindustry/content/StatusEffects.java b/core/src/io/anuke/mindustry/content/StatusEffects.java index fb94a881ce..e4ed8a921b 100644 --- a/core/src/io/anuke/mindustry/content/StatusEffects.java +++ b/core/src/io/anuke/mindustry/content/StatusEffects.java @@ -18,7 +18,7 @@ public class StatusEffects implements ContentList{ none = new StatusEffect(); burning = new StatusEffect(){{ - damage = 0.04f; + damage = 0.06f; effect = Fx.burning; opposite(() -> wet, () -> freezing); diff --git a/core/src/io/anuke/mindustry/content/TechTree.java b/core/src/io/anuke/mindustry/content/TechTree.java index 21ca9ff84b..2ab337bf44 100644 --- a/core/src/io/anuke/mindustry/content/TechTree.java +++ b/core/src/io/anuke/mindustry/content/TechTree.java @@ -317,9 +317,9 @@ public class TechTree implements ContentList{ return node(block, () -> {}); } - public static void create(Block parent, Block block){ + public static TechNode create(Block parent, Block block){ TechNode.context = all.find(t -> t.block == parent); - node(block, () -> {}); + return node(block, () -> {}); } public static class TechNode{ diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index bfedda548c..c2fad7a15f 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -8,9 +8,9 @@ import io.anuke.arc.function.*; import io.anuke.arc.math.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.util.serialization.*; -import io.anuke.mindustry.maps.*; import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Net.*; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.ui.dialogs.*; import static io.anuke.mindustry.Vars.mobile; @@ -24,27 +24,18 @@ public interface Platform{ default void inviteFriends(){} /** Steam: Share a map on the workshop.*/ - default void publishMap(Map map){} - - /** Steam: Return external workshop maps to be loaded.*/ - default Array getExternalMaps(){ - return Array.with(); - } - - /** Steam: Return external workshop mods to be loaded.*/ - default Array getExternalMods(){ - return Array.with(); - } - - /** Steam: View a map listing on the workshop.*/ - default void viewMapListing(Map map){} + default void publish(Publishable pub){} /** Steam: View a listing on the workshop.*/ - default void viewListing(String mapid){} + default void viewListing(Publishable pub){} - /** Steam: View map workshop info, removing the map ID tag if its listing is deleted. - * Also presents the option to update the map. */ - default void viewMapListingInfo(Map map){} + /** Steam: View a listing on the workshop by an ID.*/ + default void viewListingID(String mapid){} + + /** Steam: Return external workshop maps to be loaded.*/ + default Array getWorkshopContent(Class type){ + return new Array<>(0); + } /** Steam: Open workshop for maps.*/ default void openWorkshop(){} diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 57ad4cccda..107971e414 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{ public DeployDialog deploy; public TechTreeDialog tech; public MinimapDialog minimap; + public SchematicsDialog schematics; public ModsDialog mods; public Cursor drillCursor, unloadCursor; @@ -185,6 +186,13 @@ public class UI implements ApplicationListener, Loadable{ Core.scene.act(); Core.scene.draw(); + if(Core.input.keyTap(KeyCode.MOUSE_LEFT) && Core.scene.getKeyboardFocus() instanceof TextField){ + Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true); + if(!(e instanceof TextField)){ + Core.scene.setKeyboardFocus(null); + } + } + //draw overlay for buttons if(state.rules.tutorial){ control.tutorial.draw(); @@ -225,6 +233,7 @@ public class UI implements ApplicationListener, Loadable{ tech = new TechTreeDialog(); minimap = new MinimapDialog(); mods = new ModsDialog(); + schematics = new SchematicsDialog(); Group group = Core.scene.root; @@ -296,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){ @@ -307,7 +316,7 @@ public class UI implements ApplicationListener, Loadable{ Table table = new Table(); table.setFillParent(true); table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.remove()); - table.top().add(info).padTop(10); + table.top().add(info).style(Styles.outlineLabel).padTop(10); Core.scene.add(table); } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 88fe62e604..587ce46120 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -149,15 +149,16 @@ public class MapEditorDialog extends Dialog implements Disposable{ if(steam){ menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> { Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim())); + if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){ - platform.viewListing(editor.getTags().get("steamid")); + platform.viewListingID(editor.getTags().get("steamid")); return; } Map map = save(); if(editor.getTags().containsKey("steamid") && map != null){ - platform.viewMapListingInfo(map); + platform.viewListing(map); return; } @@ -173,7 +174,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ return; } - platform.publishMap(map); + platform.publish(map); }).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop")); menu.cont.row(); diff --git a/core/src/io/anuke/mindustry/entities/bullet/BulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java index 58b186c28d..571be44bb0 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/BulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java @@ -47,7 +47,7 @@ public abstract class BulletType extends Content{ /** Status effect applied on hit. */ public StatusEffect status = StatusEffects.none; /** Intensity of applied status effect in terms of duration. */ - public float statusDuration = 60 * 1f; + public float statusDuration = 60 * 10f; /** Whether this bullet type collides with tiles. */ public boolean collidesTiles = true; /** Whether this bullet type collides with tiles that are of the same team. */ diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index 7fe7aea771..482bfb653f 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -104,7 +104,7 @@ public interface BuilderTrait extends Entity, TeamTrait{ if(current.breaking){ entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); }else{ - if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier)){ + if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier, current.hasConfig)){ if(current.hasConfig){ Call.onTileConfig(null, tile, current.config); } @@ -267,18 +267,26 @@ public interface BuilderTrait extends Entity, TeamTrait{ /** Class for storing build requests. Can be either a place or remove request. */ class BuildRequest{ + /** Position and rotation of this request. */ public int x, y, rotation; + /** Block being placed. If null, this is a breaking request.*/ public @Nullable Block block; + /** Whether this is a break request.*/ public boolean breaking; + /** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/ public boolean hasConfig; + /** Config int. Not used unless hasConfig is true.*/ public int config; + /** Original position, only used in schematics.*/ + public int originalX, originalY, originalWidth, originalHeight; + /** Last progress.*/ public float progress; + /** Whether construction has started for this request.*/ public boolean initialized; - //animation variables + /** Visual scale. Used only for rendering.*/ public float animScale = 0f; - public float animInvalid; /** This creates a build request. */ public BuildRequest(int x, int y, int rotation, Block block){ @@ -302,6 +310,31 @@ public interface BuilderTrait extends Entity, TeamTrait{ } + public BuildRequest copy(){ + BuildRequest copy = new BuildRequest(); + copy.x = x; + copy.y = y; + copy.rotation = rotation; + copy.block = block; + copy.breaking = breaking; + copy.hasConfig = hasConfig; + copy.config = config; + copy.originalX = originalX; + copy.originalY = originalY; + copy.progress = progress; + copy.initialized = initialized; + copy.animScale = animScale; + return copy; + } + + public BuildRequest original(int x, int y, int originalWidth, int originalHeight){ + originalX = x; + originalY = y; + this.originalWidth = originalWidth; + this.originalHeight = originalHeight; + return this; + } + public Rectangle bounds(Rectangle rect){ if(breaking){ return rect.set(-100f, -100f, 0f, 0f); diff --git a/core/src/io/anuke/mindustry/entities/type/BaseEntity.java b/core/src/io/anuke/mindustry/entities/type/BaseEntity.java index ee938afd39..97de965cfe 100755 --- a/core/src/io/anuke/mindustry/entities/type/BaseEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/BaseEntity.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.entities.type; +import io.anuke.mindustry.*; import io.anuke.mindustry.entities.EntityGroup; import io.anuke.mindustry.entities.traits.Entity; @@ -14,6 +15,14 @@ public abstract class BaseEntity implements Entity{ id = lastid++; } + public int tileX(){ + return Vars.world.toTile(x); + } + + public int tileY(){ + return Vars.world.toTile(y); + } + @Override public int getID(){ return id; diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 84718b1af9..2940897d1e 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -801,6 +801,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ textFadeTime = 0f; target = null; moveTarget = null; + isShooting = isBoosting = isTransferring = isTyping = false; spawner = lastSpawner = null; health = maxHealth(); mining = null; diff --git a/core/src/io/anuke/mindustry/game/GlobalData.java b/core/src/io/anuke/mindustry/game/GlobalData.java index 5ef1ae09fb..f0b7bfecab 100644 --- a/core/src/io/anuke/mindustry/game/GlobalData.java +++ b/core/src/io/anuke/mindustry/game/GlobalData.java @@ -39,6 +39,9 @@ public class GlobalData{ files.add(Core.settings.getSettingsFile()); files.addAll(customMapDirectory.list()); files.addAll(saveDirectory.list()); + files.addAll(screenshotDirectory.list()); + files.addAll(modDirectory.list()); + files.addAll(schematicDirectory.list()); String base = Core.settings.getDataDirectory().path(); try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){ diff --git a/core/src/io/anuke/mindustry/game/Schematic.java b/core/src/io/anuke/mindustry/game/Schematic.java new file mode 100644 index 0000000000..ff5f533d05 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Schematic.java @@ -0,0 +1,111 @@ +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.game.Schematics.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; + +import static io.anuke.mindustry.Vars.*; + +public class Schematic implements Publishable{ + public final Array tiles; + public StringMap tags; + public int width, height; + public @Nullable FileHandle file; + + public Schematic(Array tiles, StringMap tags, int width, int height){ + this.tiles = tiles; + this.tags = tags; + this.width = width; + 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 void save(){ + schematics.saveChanges(this); + } + + @Override + public String getSteamID(){ + return tags.get("steamid"); + } + + @Override + public void addSteamID(String id){ + tags.put("steamid", id); + save(); + } + + @Override + public void removeSteamID(){ + tags.remove("steamid"); + save(); + } + + @Override + public String steamTitle(){ + return name(); + } + + @Override + public String steamDescription(){ + return null; + } + + @Override + public String steamTag(){ + return "schematic"; + } + + @Override + public FileHandle createSteamFolder(String id){ + FileHandle directory = tmpDirectory.child("schematic_" + id).child("schematic." + schematicExtension); + file.copyTo(directory); + return directory; + } + + @Override + public FileHandle createSteamPreview(String id){ + FileHandle preview = tmpDirectory.child("schematic_preview_" + id + ".png"); + schematics.savePreview(this, PreviewRes.high, preview); + return preview; + } + + public static class Stile{ + public @NonNull Block block; + public short x, y; + public int config; + public byte rotation; + + public Stile(Block block, int x, int y, int config, byte rotation){ + this.block = block; + this.x = (short)x; + this.y = (short)y; + this.config = config; + this.rotation = rotation; + } + } +} diff --git a/core/src/io/anuke/mindustry/game/Schematics.java b/core/src/io/anuke/mindustry/game/Schematics.java new file mode 100644 index 0000000000..9cadd1e090 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Schematics.java @@ -0,0 +1,380 @@ +package io.anuke.mindustry.game; + +import io.anuke.arc.*; +import io.anuke.arc.assets.*; +import io.anuke.arc.collection.*; +import io.anuke.arc.files.*; +import io.anuke.arc.graphics.*; +import io.anuke.arc.graphics.g2d.*; +import io.anuke.arc.graphics.glutils.*; +import io.anuke.arc.util.*; +import io.anuke.arc.util.io.Streams.*; +import io.anuke.arc.util.serialization.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.game.Schematic.*; +import io.anuke.mindustry.input.*; +import io.anuke.mindustry.input.PlaceUtils.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; + +import java.io.*; +import java.util.zip.*; + +import static io.anuke.mindustry.Vars.*; + +/** Handles schematics.*/ +public class Schematics implements Loadable{ + private static final byte[] header = {'m', 's', 'c', 'h'}; + private static final byte version = 0; + + private static final int padding = 2; + + private OptimizedByteArrayOutputStream out = new OptimizedByteArrayOutputStream(1024); + private Array all = new Array<>(); + private OrderedMap> previews = new OrderedMap<>(); + private FrameBuffer shadowBuffer; + + public Schematics(){ + Events.on(DisposeEvent.class, e -> { + previews.each((schem, m) -> m.each((res, buffer) -> buffer.dispose())); + previews.clear(); + shadowBuffer.dispose(); + }); + } + + @Override + public void loadSync(){ + load(); + } + + /** Load all schematics in the folder immediately.*/ + public void load(){ + all.clear(); + + for(FileHandle file : schematicDirectory.list()){ + loadFile(file); + } + + platform.getWorkshopContent(Schematic.class).each(this::loadFile); + + Core.app.post(() -> { + shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 2, maxSchematicSize + padding + 2); + }); + } + + private void loadFile(FileHandle file){ + if(!file.extension().equals(schematicExtension)) return; + + try{ + Schematic s = read(file); + all.add(s); + + //external file from workshop + if(!s.file.parent().equals(schematicDirectory)){ + s.tags.put("steamid", s.file.parent().name()); + } + }catch(IOException e){ + Log.err(e); + } + } + + public Array all(){ + return all; + } + + public void saveChanges(Schematic s){ + if(s.file != null){ + try{ + write(s, s.file); + }catch(Exception e){ + ui.showException(e); + } + } + } + + public void savePreview(Schematic schematic, PreviewRes res, FileHandle file){ + FrameBuffer buffer = getBuffer(schematic, res); + Draw.flush(); + buffer.begin(); + Pixmap pixmap = ScreenUtils.getFrameBufferPixmap(0, 0, buffer.getWidth(), buffer.getHeight()); + file.writePNG(pixmap); + buffer.end(); + } + + public Texture getPreview(Schematic schematic, PreviewRes res){ + return getBuffer(schematic, res).getTexture(); + } + + public FrameBuffer getBuffer(Schematic schematic, PreviewRes res){ + if(!previews.getOr(schematic, ObjectMap::new).containsKey(res)){ + int resolution = res.resolution; + Draw.blend(); + Draw.reset(); + Time.mark(); + Tmp.m1.set(Draw.proj()); + Tmp.m2.set(Draw.trans()); + FrameBuffer buffer = new FrameBuffer((schematic.width + padding) * resolution, (schematic.height + padding) * resolution); + + shadowBuffer.beginDraw(Color.clear); + + Draw.trans().idt(); + Draw.proj().setOrtho(0, 0, shadowBuffer.getWidth(), shadowBuffer.getHeight()); + + Draw.color(); + schematic.tiles.each(t -> { + int size = t.block.size; + int offsetx = -(size - 1) / 2; + int offsety = -(size - 1) / 2; + for(int dx = 0; dx < size; dx++){ + for(int dy = 0; dy < size; dy++){ + int wx = t.x + dx + offsetx; + int wy = t.y + dy + offsety; + Fill.square(padding/2f + wx + 0.5f, padding/2f + wy + 0.5f, 0.5f); + } + } + }); + + shadowBuffer.endDraw(); + + buffer.beginDraw(Color.clear); + + Draw.proj().setOrtho(0, buffer.getHeight(), buffer.getWidth(), -buffer.getHeight()); + + Tmp.tr1.set(shadowBuffer.getTexture(), 0, 0, schematic.width + padding, schematic.height + padding); + Draw.color(0f, 0f, 0f, 1f); + Draw.rect(Tmp.tr1, buffer.getWidth()/2f, buffer.getHeight()/2f, buffer.getWidth(), -buffer.getHeight()); + Draw.color(); + + Array requests = schematic.tiles.map(t -> new BuildRequest(t.x, t.y, t.rotation, t.block).configure(t.config)); + + Draw.flush(); + //scale each request to fit schematic + Draw.trans().scale(resolution / tilesize, resolution / tilesize).translate(tilesize*1.5f, tilesize*1.5f); + + //draw requests + requests.each(req -> { + req.animScale = 1f; + req.block.drawRequestRegion(req, requests::each); + }); + + requests.each(req -> req.block.drawRequestConfigTop(req, requests::each)); + + Draw.flush(); + Draw.trans().idt(); + + buffer.endDraw(); + + Draw.proj(Tmp.m1); + Draw.trans(Tmp.m2); + + previews.getOr(schematic, ObjectMap::new).put(res, buffer); + Log.info("Time taken: {0}", Time.elapsed()); + } + + return previews.get(schematic).get(res); + } + + /** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */ + public Array toRequests(Schematic schem, int x, int y){ + return schem.tiles.map(t -> new BuildRequest(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config)).removeAll(s -> !s.block.isVisible()); + } + + /** Adds a schematic to the list, also copying it into the files.*/ + public void add(Schematic schematic){ + all.add(schematic); + try{ + write(schematic, schematicDirectory.child(Time.millis() + "." + schematicExtension)); + }catch(IOException e){ + Log.err(e); + } + } + + 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); + x = result.x; + y = result.y; + x2 = result.x2; + y2 = result.y2; + + Array tiles = new Array<>(); + + int minx = x2, miny = y2, maxx = x, maxy = y; + boolean found = false; + for(int cx = x; cx <= x2; cx++){ + for(int cy = y; cy <= y2; cy++){ + Tile linked = world.ltile(cx, cy); + + if(linked != null && linked.entity != null && linked.entity.block.isVisible()){ + int top = linked.block().size/2; + int bot = linked.block().size % 2 == 1 ? -linked.block().size/2 : -(linked.block().size - 1)/2; + minx = Math.min(linked.x + bot, minx); + miny = Math.min(linked.y + bot, miny); + maxx = Math.max(linked.x + top, maxx); + maxy = Math.max(linked.y + top, maxy); + found = true; + } + } + } + + if(found){ + x = minx; + y = miny; + x2 = maxx; + y2 = maxy; + }else{ + return new Schematic(new Array<>(), new StringMap(), 1, 1); + } + + int width = x2 - x + 1, height = y2 - y + 1; + int offsetX = -x, offsetY = -y; + for(int cx = x; cx <= x2; cx++){ + for(int cy = y; cy <= y2; cy++){ + Tile tile = world.tile(cx, cy); + + if(tile != null && tile.entity != null){ + int config = tile.entity.config(); + if(tile.block().posConfig){ + config = Pos.get(Pos.x(config) + offsetX, Pos.y(config) + offsetY); + } + + tiles.add(new Stile(tile.block(), cx + offsetX, cy + offsetY, config, tile.rotation())); + } + } + } + + return new Schematic(tiles, new StringMap(), width, height); + } + + /** Converts a schematic to base64. Note that the result of this will always start with 'bXNjaAB'.*/ + public String writeBase64(Schematic schematic){ + try{ + out.reset(); + write(schematic, out); + return new String(Base64Coder.encode(out.getBuffer(), out.size())); + }catch(IOException e){ + throw new RuntimeException(e); + } + } + + /** Loads a schematic from base64. May throw an exception. */ + public Schematic readBase64(String schematic) throws IOException{ + return read(new ByteArrayInputStream(Base64Coder.decode(schematic))); + } + + //region IO methods + + public static Schematic read(FileHandle file) throws IOException{ + Schematic s = read(new DataInputStream(file.read(1024))); + if(!s.tags.containsKey("name")){ + s.tags.put("name", file.nameWithoutExtension()); + } + s.file = file; + return s; + } + + public static Schematic read(InputStream input) throws IOException{ + for(byte b : header){ + if(input.read() != b){ + throw new IOException("Not a schematic file (missing header)."); + } + } + + int ver; + if((ver = input.read()) != version){ + throw new IOException("Unknown version: " + ver); + } + + try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){ + short width = stream.readShort(), height = stream.readShort(); + + StringMap map = new StringMap(); + byte tags = stream.readByte(); + for(int i = 0; i < tags; i++){ + map.put(stream.readUTF(), stream.readUTF()); + } + + IntMap blocks = new IntMap<>(); + byte length = stream.readByte(); + for(int i = 0; i < length; i++){ + Block block = Vars.content.getByName(ContentType.block, stream.readUTF()); + blocks.put(i, block == null ? Blocks.air : block); + } + + int total = stream.readInt(); + Array tiles = new Array<>(total); + for(int i = 0; i < total; i++){ + Block block = blocks.get(stream.readByte()); + int position = stream.readInt(); + int config = stream.readInt(); + byte rotation = stream.readByte(); + if(block != Blocks.air){ + tiles.add(new Stile(block, Pos.x(position), Pos.y(position), config, rotation)); + } + } + + return new Schematic(tiles, map, width, height); + } + } + + public static void write(Schematic schematic, FileHandle file) throws IOException{ + write(schematic, file.write(false, 1024)); + } + + public static void write(Schematic schematic, OutputStream output) throws IOException{ + output.write(header); + output.write(version); + + try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){ + + stream.writeShort(schematic.width); + stream.writeShort(schematic.height); + + stream.writeByte(schematic.tags.size); + for(ObjectMap.Entry e : schematic.tags.entries()){ + stream.writeUTF(e.key); + stream.writeUTF(e.value); + } + + OrderedSet blocks = new OrderedSet<>(); + schematic.tiles.each(t -> blocks.add(t.block)); + + //create dictionary + stream.writeByte(blocks.size); + for(int i = 0; i < blocks.size; i++){ + stream.writeUTF(blocks.orderedItems().get(i).name); + } + + stream.writeInt(schematic.tiles.size); + //write each tile + for(Stile tile : schematic.tiles){ + stream.writeByte(blocks.orderedItems().indexOf(tile.block)); + stream.writeInt(Pos.get(tile.x, tile.y)); + stream.writeInt(tile.config); + stream.writeByte(tile.rotation); + } + } + } + + //endregion + + public enum PreviewRes{ + low(8), med(8), high(32); + + public final int resolution; + + PreviewRes(int resolution){ + this.resolution = resolution; + } + } +} diff --git a/core/src/io/anuke/mindustry/game/Tutorial.java b/core/src/io/anuke/mindustry/game/Tutorial.java index 8eb8e9e054..f402f88a5b 100644 --- a/core/src/io/anuke/mindustry/game/Tutorial.java +++ b/core/src/io/anuke/mindustry/game/Tutorial.java @@ -40,6 +40,10 @@ public class Tutorial{ Events.on(BlockInfoEvent.class, event -> events.add("blockinfo")); Events.on(DepositEvent.class, event -> events.add("deposit")); Events.on(WithdrawEvent.class, event -> events.add("withdraw")); + + for(TutorialStage stage : TutorialStage.values()){ + stage.load(); + } } /** update tutorial state, transition if needed */ @@ -204,13 +208,17 @@ public class Tutorial{ /** displayed tutorial stage text.*/ public String text(){ if(sentences == null){ - this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name(); - this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty()); + load(); } String line = sentences.get(control.tutorial.sentence); return line.contains("{") ? text.get(line) : line; } + void load(){ + this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name(); + this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty()); + } + /** called every frame when this stage is active.*/ void update(){ diff --git a/core/src/io/anuke/mindustry/input/Binding.java b/core/src/io/anuke/mindustry/input/Binding.java index 90309b74ad..bd7cc9b4ac 100644 --- a/core/src/io/anuke/mindustry/input/Binding.java +++ b/core/src/io/anuke/mindustry/input/Binding.java @@ -18,6 +18,10 @@ public enum Binding implements KeyBind{ rotateplaced(KeyCode.R), diagonal_placement(KeyCode.CONTROL_LEFT), pick(KeyCode.MOUSE_MIDDLE), + schematic_select(KeyCode.F), + schematic_flip_x(KeyCode.Z), + schematic_flip_y(KeyCode.X), + schematic_menu(KeyCode.T), dash(KeyCode.SHIFT_LEFT), gridMode(KeyCode.BACKTICK), gridModeShift(KeyCode.ALT_LEFT), diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 575ee5c134..b705551709 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -6,11 +6,15 @@ import io.anuke.arc.Graphics.Cursor.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.scene.*; +import io.anuke.arc.scene.event.*; import io.anuke.arc.scene.ui.*; +import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.*; import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.ui.*; @@ -24,9 +28,9 @@ public class DesktopInput extends InputHandler{ /** Current cursor type. */ private Cursor cursorType = SystemCursor.arrow; /** Position where the player started dragging a line. */ - private int selectX, selectY; + private int selectX, selectY, schemX, schemY; /** Last known line positions.*/ - private int lastLineX, lastLineY; + private int lastLineX, lastLineY, schematicX, schematicY; /** Whether selecting mode is active. */ private PlaceMode mode; /** Animation scale for line. */ @@ -40,14 +44,39 @@ public class DesktopInput extends InputHandler{ public void buildUI(Group group){ group.fill(t -> { t.bottom().update(() -> t.getColor().a = Mathf.lerpDelta(t.getColor().a, player.isBuilding() ? 1f : 0f, 0.15f)); - t.visible(() -> Core.settings.getBool("hints")); + t.visible(() -> Core.settings.getBool("hints") && selectRequests.isEmpty()); t.table(Styles.black6, b -> { b.defaults().left(); b.label(() -> Core.bundle.format(!player.isBuilding ? "resumebuilding" : "pausebuilding", Core.keybinds.get(Binding.pause_building).key.name())).style(Styles.outlineLabel); b.row(); b.add(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.name())).style(Styles.outlineLabel); + b.row(); + b.add(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.name())).style(Styles.outlineLabel); }).margin(10f); }); + + group.fill(t -> { + t.visible(() -> lastSchematic != null && !selectRequests.isEmpty()); + t.bottom(); + t.table(Styles.black6, b -> { + b.touchable(Touchable.enabled); + b.defaults().left(); + b.add(Core.bundle.format("schematic.flip", + Core.keybinds.get(Binding.schematic_flip_x).key.name(), + Core.keybinds.get(Binding.schematic_flip_y).key.name())).style(Styles.outlineLabel); + b.row(); + b.table(a -> { + a.addImageTextButton("$schematic.add", Icon.saveSmall, () -> { + ui.showTextInput("$schematic.add", "$name", "", text -> { + lastSchematic.tags.put("name", text); + schematics.add(lastSchematic); + ui.showInfoFade("$schematic.saved"); + ui.schematics.showInfo(lastSchematic); + }); + }).colspan(2).size(250f, 50f); + }); + }).margin(6f); + }); } @Override @@ -66,7 +95,7 @@ public class DesktopInput extends InputHandler{ drawRequest(lineRequests.get(i)); } }else if(mode == breaking){ - drawSelection(selectX, selectY, cursorX, cursorY); + drawBreakSelection(selectX, selectY, cursorX, cursorY); }else if(isPlacing()){ if(block.rotate){ drawArrow(block, cursorX, cursorY, rotation); @@ -83,6 +112,12 @@ public class DesktopInput extends InputHandler{ } } + //draw schematic requests + for(BuildRequest request : selectRequests){ + request.animScale = 1f; + drawRequest(request); + } + if(sreq != null){ boolean valid = validPlace(sreq.x, sreq.y, sreq.block, sreq.rotation, sreq); if(sreq.block.rotate){ @@ -94,6 +129,10 @@ public class DesktopInput extends InputHandler{ drawSelected(sreq.x, sreq.y, sreq.block, getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null ? Pal.remove : Pal.accent); } + if(Core.input.keyDown(Binding.schematic_select)){ + drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize); + } + Draw.reset(); } @@ -118,7 +157,7 @@ public class DesktopInput extends InputHandler{ if(state.is(State.menu) || Core.scene.hasDialog()) return; //zoom things - if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && (Core.input.keyDown(Binding.zoom_hold))){ + if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && Core.input.keyDown(Binding.zoom_hold)){ renderer.scaleCamera(Core.input.axisTap(Binding.zoom)); } @@ -134,6 +173,10 @@ public class DesktopInput extends InputHandler{ mode = none; } + if(mode != none){ + selectRequests.clear(); + } + if(player.isShooting && !canShoot()){ player.isShooting = false; } @@ -151,8 +194,12 @@ public class DesktopInput extends InputHandler{ sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4); } - if(Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0 && isPlacing() && mode == placing){ - updateLine(selectX, selectY); + if(!Core.input.keyDown(Binding.zoom_hold) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){ + if(isPlacing() && mode == placing){ + updateLine(selectX, selectY); + }else if(!selectRequests.isEmpty()){ + rotateRequests(selectRequests, (int)Core.input.axisTap(Binding.rotate)); + } } Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY()); @@ -162,7 +209,7 @@ public class DesktopInput extends InputHandler{ cursorType = cursor.block().getCursor(cursor); - if(isPlacing()){ + if(isPlacing() || !selectRequests.isEmpty()){ cursorType = SystemCursor.hand; } @@ -190,15 +237,49 @@ public class DesktopInput extends InputHandler{ cursorType = SystemCursor.arrow; } + @Override + public void useSchematic(Schematic schem){ + block = null; + schematicX = tileX(getMouseX()); + schematicY = tileY(getMouseY()); + + selectRequests.addAll(schematics.toRequests(schem, schematicX, schematicY)); + mode = none; + } + @Override public boolean isBreaking(){ return mode == breaking; } + @Override + public void buildPlacementUI(Table table){ + table.addImage().color(Pal.gray).height(4f).colspan(4).growX(); + table.row(); + table.left().margin(0f).defaults().size(48f).left(); + + table.addImageButton(Icon.wikiSmall, Styles.clearPartiali, () -> { + ui.schematics.show(); + }); + } + void pollInput(){ Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY()); int cursorX = tileX(Core.input.mouseX()); int cursorY = tileY(Core.input.mouseY()); + int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y); + + if(!selectRequests.isEmpty()){ + int shiftX = rawCursorX - schematicX, shiftY = rawCursorY - schematicY; + + selectRequests.each(s -> { + s.x += shiftX; + s.y += shiftY; + }); + + schematicX += shiftX; + schematicY += shiftY; + } if(Core.input.keyTap(Binding.deselect)){ player.setMineTile(null); @@ -208,6 +289,38 @@ public class DesktopInput extends InputHandler{ player.clearBuilding(); } + if(Core.input.keyTap(Binding.schematic_select)){ + schemX = rawCursorX; + schemY = rawCursorY; + } + + if(Core.input.keyTap(Binding.schematic_menu)){ + ui.schematics.show(); + } + + if(Core.input.keyTap(Binding.clear_building)){ + lastSchematic = null; + selectRequests.clear(); + } + + if(Core.input.keyRelease(Binding.schematic_select)){ + lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY); + useSchematic(lastSchematic); + if(selectRequests.isEmpty()){ + lastSchematic = null; + } + } + + if(!selectRequests.isEmpty()){ + if(Core.input.keyTap(Binding.schematic_flip_x)){ + flipRequests(selectRequests, true); + } + + if(Core.input.keyTap(Binding.schematic_flip_y)){ + flipRequests(selectRequests, false); + } + } + if(sreq != null){ float offset = ((sreq.block.size + 2) % 2) * tilesize / 2f; float x = Core.input.mouseWorld().x + offset; @@ -233,7 +346,10 @@ public class DesktopInput extends InputHandler{ if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){ BuildRequest req = getRequest(cursorX, cursorY); - if(isPlacing()){ + if(!selectRequests.isEmpty()){ + flushRequests(selectRequests); + //selectRequests.clear(); + }else if(isPlacing()){ selectX = cursorX; selectY = cursorY; lastLineX = cursorX; @@ -329,6 +445,7 @@ public class DesktopInput extends InputHandler{ mode = none; block = null; sreq = null; + selectRequests.clear(); } } } diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 4e7650e8ca..cfdafc57fd 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -14,12 +14,14 @@ import io.anuke.arc.scene.*; import io.anuke.arc.scene.event.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.effect.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.Teams.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; @@ -51,6 +53,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ public boolean droppingItem; public Group uiGroup; + protected @Nullable Schematic lastSchematic; protected GestureDetector detector; protected PlaceLine line = new PlaceLine(); protected BuildRequest resultreq; @@ -207,7 +210,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ if(request.breaking){ drawBreaking(request.x, request.y); }else{ - drawSelected(request.x, request.y, request.tile().block(), Pal.remove); + drawSelected(request.x, request.y, request.block, Pal.remove); } } @@ -219,6 +222,76 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ drawSelected(x, y, block, Pal.remove); } + public void useSchematic(Schematic schem){ + selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y))); + } + + public void rotateRequests(Array requests, int direction){ + int ox = rawTileX(), oy = rawTileY(); + + requests.each(req -> { + //rotate config position + if(req.block.posConfig){ + int cx = Pos.x(req.config) - req.originalX, cy = Pos.y(req.config) - req.originalY; + int lx = cx; + + if(direction >= 0){ + cx = -cy; + cy = lx; + }else{ + cx = cy; + cy = -lx; + } + req.config = Pos.get(cx + req.originalX, cy + req.originalY); + } + + //rotate actual request, centered on its multiblock position + float wx = (req.x - ox) * tilesize + req.block.offset(), wy = (req.y - oy) * tilesize + req.block.offset(); + float x = wx; + if(direction >= 0){ + wx = -wy; + wy = x; + }else{ + wx = wy; + wy = -x; + } + req.x = world.toTile(wx - req.block.offset()) + ox; + req.y = world.toTile(wy - req.block.offset()) + oy; + req.rotation = Mathf.mod(req.rotation + direction, 4); + }); + } + + public void flipRequests(Array requests, boolean x){ + int origin = x ? rawTileX() : rawTileY(); + + requests.each(req -> { + int value = -((x ? req.x : req.y) - origin) + origin; + + if(x){ + req.x = value; + }else{ + req.y = value; + } + + if(req.block.posConfig){ + int corigin = x ? req.originalWidth/2 : req.originalHeight/2; + int nvalue = -((x ? Pos.x(req.config) : Pos.y(req.config)) - corigin) + corigin; + if(x){ + req.originalX = -(req.originalX - corigin) + corigin; + req.config = Pos.get(nvalue, Pos.y(req.config)); + }else{ + req.originalY = -(req.originalY - corigin) + corigin; + req.config = Pos.get(Pos.x(req.config), nvalue); + } + } + + //flip rotation + if(x == (req.rotation % 2 == 0)){ + req.rotation = Mathf.mod(req.rotation + 2, 4); + } + }); + } + /** Returns the selection request that overlaps this position, or null. */ protected BuildRequest getRequest(int x, int y){ return getRequest(x, y, 1, null); @@ -259,7 +332,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ return null; } - protected void drawSelection(int x1, int y1, int x2, int y2){ + protected void drawBreakSelection(int x1, int y1, int x2, int y2){ NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f); NormalizeResult dresult = PlaceUtils.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength); @@ -307,10 +380,21 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); } + protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){ + NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f); + + Lines.stroke(2f); + + Draw.color(Pal.accentBack); + Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y); + Draw.color(Pal.accent); + Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); + } + protected void flushSelectRequests(Array requests){ for(BuildRequest req : requests){ if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){ - selectRequests.add(req); + selectRequests.add(req.copy()); } } } @@ -318,7 +402,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ protected void flushRequests(Array requests){ for(BuildRequest req : requests){ if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){ - player.addBuildRequest(req); + BuildRequest copy = req.copy(); + if(copy.hasConfig && copy.block.posConfig){ + copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY); + } + player.addBuildRequest(copy); } } } @@ -448,14 +536,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } } - /* - //clear when the player taps on something else - if(!consumed && !mobile && player.isBuilding() && block == null){ - //player.clearBuilding(); - block = null; - return true; - }*/ - if(!showedInventory){ frag.inv.hide(); } @@ -499,6 +579,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ return world.tile(tileX(x), tileY(y)); } + int rawTileX(){ + return world.toTile(Core.input.mouseWorld().x); + } + + int rawTileY(){ + return world.toTile(Core.input.mouseWorld().y); + } + int tileX(float cursorX){ Vector2 vec = Core.input.mouseWorld(cursorX, 0); if(selectedBlock()){ diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java index 30872343a9..692a5f505c 100644 --- a/core/src/io/anuke/mindustry/input/MobileInput.java +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -208,7 +208,7 @@ public class MobileInput extends InputHandler implements GestureListener{ } //move all current requests to removal array so they fade out - removals.addAll(selectRequests.find(r -> !r.breaking)); + removals.addAll(selectRequests.select(r -> !r.breaking)); selectRequests.clear(); selecting = false; }).visible(() -> !selectRequests.isEmpty()).name("confirmplace"); @@ -243,7 +243,6 @@ public class MobileInput extends InputHandler implements GestureListener{ if(tile == null) continue; request.animScale = Mathf.lerpDelta(request.animScale, 0f, 0.2f); - request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0f, 0.2f); if(request.breaking){ drawSelected(request.x, request.y, tile.block(), Pal.remove); @@ -263,10 +262,8 @@ public class MobileInput extends InputHandler implements GestureListener{ if((!request.breaking && validPlace(tile.x, tile.y, request.block, request.rotation)) || (request.breaking && validBreak(tile.x, tile.y))){ request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.2f); - request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0f, 0.2f); }else{ request.animScale = Mathf.lerpDelta(request.animScale, 0.6f, 0.1f); - request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0.9f, 0.2f); } Tmp.c1.set(Draw.getMixColor()); @@ -305,7 +302,7 @@ public class MobileInput extends InputHandler implements GestureListener{ drawRequest(lineRequests.get(i)); } }else if(mode == breaking){ - drawSelection(lineStartX, lineStartY, tileX, tileY); + drawBreakSelection(lineStartX, lineStartY, tileX, tileY); } } @@ -340,7 +337,7 @@ public class MobileInput extends InputHandler implements GestureListener{ if(request.breaking){ drawSelected(request.x, request.y, request.tile().block(), Pal.remove); }else{ - drawRequest(request.x, request.y, request.block, request.rotation); + request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation)); drawSelected(request.x, request.y, request.block, Pal.accent); } } diff --git a/core/src/io/anuke/mindustry/input/PlaceUtils.java b/core/src/io/anuke/mindustry/input/PlaceUtils.java index 52e06e518b..79a358e286 100644 --- a/core/src/io/anuke/mindustry/input/PlaceUtils.java +++ b/core/src/io/anuke/mindustry/input/PlaceUtils.java @@ -95,14 +95,14 @@ public class PlaceUtils{ }else{ endx = tilex; } + } - if(Math.abs(endx - tilex) > maxLength){ - endx = Mathf.sign(endx - tilex) * maxLength + tilex; - } + if(Math.abs(endx - tilex) > maxLength){ + endx = Mathf.sign(endx - tilex) * maxLength + tilex; + } - if(Math.abs(endy - tiley) > maxLength){ - endy = Mathf.sign(endy - tiley) * maxLength + tiley; - } + if(Math.abs(endy - tiley) > maxLength){ + endy = Mathf.sign(endy - tiley) * maxLength + tiley; } int dx = endx - tilex, dy = endy - tiley; @@ -141,12 +141,12 @@ public class PlaceUtils{ return result; } - static class NormalizeDrawResult{ + public static class NormalizeDrawResult{ float x, y, x2, y2; } - static class NormalizeResult{ - int x, y, x2, y2, rotation; + public static class NormalizeResult{ + public int x, y, x2, y2, rotation; boolean isX(){ return Math.abs(x2 - x) > Math.abs(y2 - y); diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 94fa841f08..378f3c8ff6 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -4,14 +4,17 @@ import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.files.*; import io.anuke.arc.graphics.*; +import io.anuke.arc.util.*; import io.anuke.mindustry.*; +import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.io.*; import io.anuke.mindustry.maps.filters.*; +import io.anuke.mindustry.type.*; -import static io.anuke.mindustry.Vars.maps; +import static io.anuke.mindustry.Vars.*; -public class Map implements Comparable{ +public class Map implements Comparable, Publishable{ /** Whether this is a custom map. */ public final boolean custom; /** Metadata. Author description, display name, etc. */ @@ -131,6 +134,76 @@ public class Map implements Comparable{ return tags.containsKey(name); } + @Override + public String getSteamID(){ + return tags.get("steamid"); + } + + @Override + public void addSteamID(String id){ + tags.put("steamid", id); + + ui.editor.editor.getTags().put("steamid", id); + try{ + ui.editor.save(); + }catch(Exception e){ + Log.err(e); + } + Events.fire(new MapPublishEvent()); + } + + @Override + public void removeSteamID(){ + tags.remove("steamid"); + + ui.editor.editor.getTags().remove("steamid"); + try{ + ui.editor.save(); + }catch(Exception e){ + Log.err(e); + } + } + + @Override + public String steamTitle(){ + return name(); + } + + @Override + public String steamDescription(){ + return description(); + } + + @Override + public String steamTag(){ + return "map"; + } + + @Override + public FileHandle createSteamFolder(String id){ + return null; + } + + @Override + public FileHandle createSteamPreview(String id){ + return null; + } + + @Override + public Array extraTags(){ + Gamemode mode = Gamemode.attack.valid(this) ? Gamemode.attack : Gamemode.survival; + return Array.with(mode.name()); + } + + @Override + public boolean prePublish(){ + tags.put("author", player.name); + ui.editor.editor.getTags().put("author", tags.get("author")); + ui.editor.save(); + + return true; + } + @Override public int compareTo(Map map){ int work = -Boolean.compare(workshop, map.workshop); diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 72d1b17b77..b19f3628ea 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -104,7 +104,7 @@ public class Maps{ } //workshop - for(FileHandle file : platform.getExternalMaps()){ + for(FileHandle file : platform.getWorkshopContent(Map.class)){ try{ Map map = loadMap(file, false); map.workshop = true; diff --git a/core/src/io/anuke/mindustry/mod/ContentParser.java b/core/src/io/anuke/mindustry/mod/ContentParser.java index f0d3dc915f..4ee77fad1f 100644 --- a/core/src/io/anuke/mindustry/mod/ContentParser.java +++ b/core/src/io/anuke/mindustry/mod/ContentParser.java @@ -15,6 +15,7 @@ import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.Json.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; +import io.anuke.mindustry.content.TechTree.*; import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.type.*; @@ -42,21 +43,12 @@ public class ContentParser{ if(data.isString()){ return field(Bullets.class, data); } - Class bc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.entities.bullets") : BasicBulletType.class; + Class bc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.entities.bullet") : BasicBulletType.class; data.remove("type"); BulletType result = make(bc); readFields(result, data); return result; }); - /* - put(Music.class, (type, data) -> { - if(fieldOpt(Musics.class, data) != null) return fieldOpt(Musics.class, data); - - String path = "music/" + data.asString() + (Vars.ios ? ".mp3" : ".ogg"); - Core.assets.load(path, Music.class); - Core.assets.finishLoadingAsset(path); - return Core.assets.get(path); - });*/ put(Sound.class, (type, data) -> { if(fieldOpt(Sounds.class, data) != null) return fieldOpt(Sounds.class, data); @@ -78,6 +70,7 @@ public class ContentParser{ /** Stores things that need to be parsed fully, e.g. reading fields of content. * This is done to accomodate binding of content names first.*/ private Array reads = new Array<>(); + private Array postreads = new Array<>(); private LoadedMod currentMod; private Content currentContent; @@ -147,6 +140,15 @@ public class ContentParser{ } currentContent = block; + + String[] research = {null}; + + //add research tech node + if(value.has("research")){ + research[0] = value.get("research").asString(); + value.remove("research"); + } + read(() -> { if(value.has("consumes")){ for(JsonValue child : value.get("consumes")){ @@ -174,8 +176,16 @@ public class ContentParser{ readFields(block, value, true); //add research tech node - if(value.has("research")){ - TechTree.create(find(ContentType.block, value.get("research").asString()), block); + if(research[0] != null){ + Block parent = find(ContentType.block, research[0]); + TechNode baseNode = TechTree.create(parent, block); + + postreads.add(() -> { + TechNode parnode = TechTree.all.find(t -> t.block == parent); + if(!parnode.children.contains(baseNode)){ + parnode.children.add(baseNode); + } + }); } //make block visible by default if there are requirements and no visibility set @@ -275,10 +285,12 @@ public class ContentParser{ public void finishParsing(){ try{ reads.each(Runnable::run); + postreads.each(Runnable::run); }catch(Exception e){ Vars.mods.handleError(new ModLoadException("Error occurred parsing content: " + currentContent, currentContent, e), currentMod); } reads.clear(); + postreads.clear(); } /** @@ -395,9 +407,7 @@ public class ContentParser{ FieldMetadata metadata = fields.get(child.name().replace(" ", "_")); if(metadata == null){ if(ignoreUnknownFields){ - if(!child.name.equals("research")){ - Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object); - } + Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object); continue; }else{ SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")"); diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java index de3ff6fd9e..2692581d2a 100644 --- a/core/src/io/anuke/mindustry/mod/Mods.java +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -60,7 +60,7 @@ public class Mods implements Loadable{ file.copyTo(dest); try{ - loaded.add(loadMod(file, false)); + loaded.add(loadMod(dest, false)); requiresReload = true; }catch(IOException e){ dest.delete(); @@ -180,7 +180,7 @@ public class Mods implements Loadable{ } //load workshop mods now - for(FileHandle file : platform.getExternalMods()){ + for(FileHandle file : platform.getWorkshopContent(LoadedMod.class)){ try{ LoadedMod mod = loadMod(file, true); if(mod.enabled()){ @@ -442,7 +442,7 @@ public class Mods implements Loadable{ } /** Represents a plugin that has been loaded from a jar file.*/ - public static class LoadedMod{ + public static class LoadedMod implements Publishable{ /** The location of this mod's zip file/folder on the disk. */ public final FileHandle file; /** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */ @@ -453,8 +453,6 @@ public class Mods implements Loadable{ public final String name; /** This mod's metadata. */ public final ModMeta meta; - /** The ID of this mod in the workshop.*/ - public @Nullable String workshopID; public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){ this.root = root; @@ -468,6 +466,63 @@ public class Mods implements Loadable{ return Core.settings.getBool(name + "-enabled", true); } + @Override + public String getSteamID(){ + return Core.settings.getString(name + "-steamid", null); + } + + @Override + public void addSteamID(String id){ + Core.settings.put(name + "-steamid", id); + Core.settings.save(); + } + + @Override + public void removeSteamID(){ + Core.settings.remove(name + "-steamid"); + Core.settings.save(); + } + + @Override + public String steamTitle(){ + return meta.name; + } + + @Override + public String steamDescription(){ + return meta.description; + } + + @Override + public String steamTag(){ + return "mod"; + } + + @Override + public FileHandle createSteamFolder(String id){ + return file; + } + + @Override + public FileHandle createSteamPreview(String id){ + return file.child("preview.png"); + } + + @Override + public boolean prePublish(){ + if(!file.isDirectory()){ + ui.showErrorMessage("$mod.folder.missing"); + return false; + } + + if(!file.child("preview.png").exists()){ + ui.showErrorMessage("$mod.preview.missing"); + return false; + } + + return true; + } + @Override public String toString(){ return "LoadedMod{" + diff --git a/core/src/io/anuke/mindustry/type/Publishable.java b/core/src/io/anuke/mindustry/type/Publishable.java new file mode 100644 index 0000000000..f3e8b45b88 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Publishable.java @@ -0,0 +1,40 @@ +package io.anuke.mindustry.type; + +import io.anuke.arc.collection.*; +import io.anuke.arc.files.*; +import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.*; + +/** Defines a piece of content that can be published on the Workshop. */ +public interface Publishable{ + /** @return workshop item ID, or null if this isn't on the workshop. */ + @Nullable String getSteamID(); + /** adds a steam ID to this item once it's published. should save the item to make sure this change is persisted. */ + void addSteamID(String id); + /** removes the item ID; called when the item isn't found. */ + void removeSteamID(); + /** @return default title of the listing. */ + String steamTitle(); + /** @return standard steam listing description, may be null. this is editable by users after release.*/ + @Nullable String steamDescription(); + /** @return the tag that this content has. e.g. 'schematic' or 'map'. */ + String steamTag(); + /** @return a folder with everything needed for this piece of content in it; does not need to be a copy. */ + FileHandle createSteamFolder(String id); + /** @return a preview file PNG. */ + FileHandle createSteamPreview(String id); + /** @return any extra tags to add to this item.*/ + default Array extraTags(){ + return new Array<>(0); + } + /** @return whether this item is or was once on the workshop.*/ + default boolean hasSteamID(){ + return getSteamID() != null && Vars.steam; + } + /** called before this item is published. + * @return true to signify that everything is cool and good, or false to significy that the user has done something wrong. + * if false is returned, make sure to show a dialog explaining the error. */ + default boolean prePublish(){ + return true; + } +} diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java index de9f1ad85e..09924218d0 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java @@ -55,6 +55,7 @@ public class FloatingDialog extends Dialog{ @Override public void addCloseButton(){ + buttons.defaults().size(210f, 64f); buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f); keyDown(key -> { diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java index 968cb0983f..a2fb508fcb 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java @@ -205,7 +205,7 @@ public class MapsDialog extends FloatingDialog{ table.addImageTextButton(map.workshop && steam ? "$view.workshop" : "$delete", map.workshop && steam ? Icon.linkSmall : Icon.trash16Small, () -> { if(map.workshop && steam){ - platform.viewMapListing(map); + platform.viewListing(map); }else{ ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name()), () -> { maps.removeMap(map); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java index f003cdc2f2..7dfe21b5c2 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java @@ -41,11 +41,11 @@ public class ModsDialog extends FloatingDialog{ mods.reloadContent(); setup(); ui.loadfrag.hide(); - }catch(Exception e){ + }catch(Throwable e){ ui.showException(e); } }); - }catch(Exception e){ + }catch(Throwable e){ ui.showException(e); } }, t -> Core.app.post(() -> ui.showException(t))); @@ -101,14 +101,20 @@ public class ModsDialog extends FloatingDialog{ setup(); }).height(50f).margin(8f).width(130f); - title.addImageButton(mod.workshopID != null ? Icon.linkSmall : Icon.trash16Small, Styles.cleari, () -> { - if(mod.workshopID == null){ + if(steam && !mod.hasSteamID()){ + title.addImageButton(Icon.loadMapSmall, Styles.cleari, () -> { + platform.publish(mod); + }).size(50f); + } + + title.addImageButton(mod.hasSteamID() ? Icon.linkSmall : Icon.trash16Small, Styles.cleari, () -> { + if(!mod.hasSteamID()){ ui.showConfirm("$confirm", "$mod.remove.confirm", () -> { mods.removeMod(mod); setup(); }); }else{ - platform.viewListing(mod.workshopID); + platform.viewListing(mod); } }).size(50f); }).growX().left().padTop(-14f).padRight(-14f); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java new file mode 100644 index 0000000000..c5e9541930 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/dialogs/SchematicsDialog.java @@ -0,0 +1,287 @@ +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.*; + +public class SchematicsDialog extends FloatingDialog{ + private SchematicInfoDialog info = new SchematicInfoDialog(); + private String search = ""; + + public SchematicsDialog(){ + super("$schematics"); + Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> { + ((Texture)t).setWrap(TextureWrap.Repeat); + }; + + shouldPause = true; + addCloseButton(); + buttons.addImageTextButton("$schematic.import", Icon.loadMapSmall, this::showImport); + shown(this::setup); + } + + void setup(){ + search = ""; + Runnable[] rebuildPane = {null}; + + cont.top(); + cont.clear(); + + cont.table(s -> { + s.left(); + s.addImage(Icon.zoom); + s.addField(search, res -> { + search = res; + rebuildPane[0].run(); + }).growX(); + }).fillX().padBottom(4); + + cont.row(); + + cont.pane(t -> { + t.top(); + t.margin(20f); + 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(0f); + b.table(buttons -> { + buttons.left(); + buttons.defaults().size(50f); + + ImageButtonStyle style = Styles.clearPartiali; + + buttons.addImageButton(Icon.infoSmall, style, () -> { + showInfo(s); + }); + + 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); + s.save(); + rebuildPane[0].run(); + }); + }); + + if(s.hasSteamID()){ + buttons.addImageButton(Icon.linkSmall, style, () -> platform.viewListing(s)); + }else{ + buttons.addImageButton(Icon.trash16Small, style, () -> { + ui.showConfirm("$confirm", "$schematic.delete.confirm", () -> { + schematics.remove(s); + rebuildPane[0].run(); + }); + }); + } + + }).growX().height(50f); + b.row(); + 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(); + + sel[0].getStyle().up = Tex.pane; + + if(++i % 4 == 0){ + t.row(); + } + } + }; + + rebuildPane[0].run(); + }).get().setScrollingDisabled(true, false); + } + + public void showInfo(Schematic schematic){ + info.show(schematic); + } + + public void showImport(){ + 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.row(); + t.addImageTextButton("$schematic.copy.import", Icon.copySmall, style, () -> { + dialog.hide(); + try{ + Schematic s = schematics.readBase64(Core.app.getClipboardText()); + schematics.add(s); + setup(); + ui.showInfoFade("$schematic.saved"); + showInfo(s); + }catch(Exception e){ + ui.showException(e); + } + }).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith(schematicBaseStart)); + t.row(); + t.addImageTextButton("$schematic.importfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(true, schematicExtension, file -> { + dialog.hide(); + + try{ + Schematic s = Schematics.read(file); + schematics.add(s); + setup(); + showInfo(s); + }catch(Exception e){ + ui.showException(e); + } + })).marginLeft(12f); + t.row(); + if(steam){ + t.addImageTextButton("$schematic.browseworkshop", Icon.wikiSmall, style, () -> { + dialog.hide(); + platform.openWorkshop(); + }).marginLeft(12f); + } + }); + }); + + dialog.addCloseButton(); + dialog.show(); + } + + 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(); + if(steam && !s.hasSteamID()){ + t.addImageTextButton("$schematic.shareworkshop", Icon.wikiSmall, style, + () -> platform.publish(s)).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; + public Color borderColor = Pal.gray; + + 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(checked ? Pal.accent : borderColor); + Draw.alpha(parentAlpha); + Lines.stroke(Scl.scl(thickness)); + Lines.rect(x, y, width, height); + Draw.reset(); + } + } + + public static class SchematicInfoDialog extends FloatingDialog{ + + SchematicInfoDialog(){ + super(""); + setFillParent(true); + addCloseButton(); + } + + public void show(Schematic schem){ + cont.clear(); + title.setText("[[" + Core.bundle.get("schematic") + "] " +schem.name()); + + 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/ui/dialogs/TechTreeDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java index b9536459d3..071879f640 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java @@ -42,6 +42,10 @@ public class TechTreeDialog extends FloatingDialog{ margin(0f).marginBottom(8); cont.stack(view = new View(), items = new ItemsDisplay()).grow(); + Events.on(ContentReloadEvent.class, e -> { + root = new TechTreeNode(TechTree.root, null); + }); + shown(() -> { checkNodes(root); treeLayout(); @@ -105,8 +109,18 @@ public class TechTreeDialog extends FloatingDialog{ RadialTreeLayout layout = new RadialTreeLayout(); LayoutNode node = new LayoutNode(root, null); layout.layout(node); - //bounds.y += nodeSize*1.5f; + float minx = 0f, miny = 0f, maxx = 0f, maxy = 0f; copyInfo(node); + + for(TechTreeNode n : nodes){ + if(!n.visible) continue; + minx = Math.min(n.x - n.width/2f, minx); + maxx = Math.max(n.x + n.width/2f, maxx); + miny = Math.min(n.y - n.height/2f, miny); + maxy = Math.max(n.y + n.height/2f, maxy); + } + bounds = new Rectangle(minx, miny, maxx - minx, maxy - miny); + bounds.y += nodeSize*1.5f; } void copyInfo(LayoutNode node){ @@ -262,7 +276,7 @@ public class TechTreeDialog extends FloatingDialog{ float rx = bounds.x + panX + ox, ry = panY + oy + bounds.y; float rw = bounds.width, rh = bounds.height; rx = Mathf.clamp(rx, -rw + pad, Core.graphics.getWidth() - pad); - ry = Mathf.clamp(ry, pad, Core.graphics.getHeight() - rh - pad); + ry = Mathf.clamp(ry, -rh + pad, Core.graphics.getHeight() - pad); panX = rx - bounds.x - ox; panY = ry - bounds.y - oy; } diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 1b34c5c7fb..f393b2e325 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -250,8 +250,9 @@ public class HudFragment extends Fragment{ }); parent.fill(t -> { + t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial); //minimap - t.add(new Minimap().visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial)); + t.add(new Minimap()); t.row(); //position t.label(() -> world.toTile(player.x) + "," + world.toTile(player.y)).style(Styles.outlineLabel) diff --git a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java index 12fbccc5a7..fc0a246851 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java @@ -22,7 +22,6 @@ public class LoadingFragment extends Fragment{ t.visible(false); t.touchable(Touchable.enabled); t.add().height(133f).row(); - t.addImage().growX().height(3f).pad(4f).growX().get().setColor(Pal.accent); t.row(); t.add("$loading").name("namelabel").pad(10f); diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index da5b3d4bc0..c6010004fd 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -87,6 +87,8 @@ public class Block extends BlockStorage{ public boolean configurable; /** Whether this block consumes touchDown events when tapped. */ public boolean consumesTap; + /** Whether the config is positional and needs to be shifted. */ + public boolean posConfig; /** * The color of this block when displayed on the minimap or map preview. * Do not set manually! This is overriden when loading for most blocks. @@ -116,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! */ @@ -676,8 +678,32 @@ public class Block extends BlockStorage{ public void drawRequestRegion(BuildRequest req, Eachable list){ TextureRegion reg = icon(Cicon.full); Draw.rect(icon(Cicon.full), req.drawx(), req.drawy(), - reg.getWidth() * req.animScale * Draw.scl, reg.getHeight() * req.animScale * Draw.scl, - !rotate ? 0 : req.rotation * 90); + reg.getWidth() * req.animScale * Draw.scl, + reg.getHeight() * req.animScale * Draw.scl, + !rotate ? 0 : req.rotation * 90); + + if(req.hasConfig){ + drawRequestConfig(req, list); + } + } + + public void drawRequestConfig(BuildRequest req, Eachable list){ + + } + + public void drawRequestConfigCenter(BuildRequest req, Content content, String region){ + Color color = content instanceof Item ? ((Item)content).color : content instanceof Liquid ? ((Liquid)content).color : null; + if(color == null) return; + + Draw.color(color); + Draw.scl *= req.animScale; + Draw.rect(region, req.drawx(), req.drawy()); + Draw.scl /= req.animScale; + Draw.color(); + } + + public void drawRequestConfigTop(BuildRequest req, Eachable list){ + } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 37c3ccc6fa..1f76ed527a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -56,7 +56,7 @@ public class BuildBlock extends Block{ } @Remote(called = Loc.server) - public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){ + public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){ if(tile == null) return; float healthf = tile.entity == null ? 1f : tile.entity.healthf(); world.setBlock(tile, block, team, rotation); @@ -70,7 +70,9 @@ public class BuildBlock extends Block{ if(!headless && builderID == player.id){ //this is run delayed, since if this is called on the server, all clients need to recieve the onBuildFinish() //event first before they can recieve the placed() event modification results - Core.app.post(() -> tile.block().playerPlaced(tile)); + if(!skipConfig){ + Core.app.post(() -> tile.block().playerPlaced(tile)); + } } Core.app.post(() -> Events.fire(new BlockBuildEndEvent(tile, playerGroup.getByID(builderID), team, false))); Sounds.place.at(tile, Mathf.random(0.7f, 1.4f)); @@ -185,7 +187,7 @@ public class BuildBlock extends Block{ private float[] accumulator; private float[] totalAccumulator; - public boolean construct(Unit builder, @Nullable TileEntity core, float amount){ + public boolean construct(Unit builder, @Nullable TileEntity core, float amount, boolean configured){ if(cblock == null){ kill(); return false; @@ -208,7 +210,7 @@ public class BuildBlock extends Block{ } if(progress >= 1f || state.rules.infiniteResources){ - Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam()); + Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam(), configured); return true; } return false; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index f8f4394482..d051adbc2d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -52,11 +52,13 @@ public class Conduit extends LiquidBlock implements Autotiler{ Draw.colorl(0.34f); Draw.alpha(0.5f); - Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90); + Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(), + botRegions[bits[0]].getWidth() * Draw.scl * req.animScale, botRegions[bits[0]].getHeight() * Draw.scl * req.animScale, + req.rotation * 90); Draw.color(); - Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90); + Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), topRegions[bits[0]].getWidth() * Draw.scl * req.animScale, topRegions[bits[0]].getHeight() * Draw.scl * req.animScale, req.rotation * 90); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index c643eef9d1..09e73ce7cb 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -107,7 +107,7 @@ public class Conveyor extends Block implements Autotiler{ if(bits == null) return; TextureRegion region = regions[bits[0]][0]; - Draw.rect(region, req.drawx(), req.drawy(), region.getWidth() * bits[1] * Draw.scl, region.getHeight() * bits[2] * Draw.scl, req.rotation * 90); + Draw.rect(region, req.drawx(), req.drawy(), region.getWidth() * bits[1] * Draw.scl * req.animScale, region.getHeight() * bits[2] * Draw.scl * req.animScale, req.rotation * 90); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index 46acbc1420..3c11b5903f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -3,11 +3,13 @@ package io.anuke.mindustry.world.blocks.distribution; import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.collection.IntSet.*; +import io.anuke.arc.function.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.util.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.type.*; @@ -23,6 +25,7 @@ public class ItemBridge extends Block{ protected int range; protected float transportTime = 2f; protected TextureRegion endRegion, bridgeRegion, arrowRegion; + protected BuildRequest otherReq; private static int lastPlaced = Pos.invalid; @@ -34,6 +37,7 @@ public class ItemBridge extends Block{ layer = Layer.power; expanded = true; itemCapacity = 10; + posConfig = true; configurable = true; hasItems = true; unloadable = false; @@ -65,6 +69,27 @@ public class ItemBridge extends Block{ arrowRegion = Core.atlas.find(name + "-arrow"); } + @Override + public void drawRequestConfigTop(BuildRequest req, Eachable list){ + otherReq = null; + list.each(other -> { + if(other.block == this && req.config == Pos.get(other.x, other.y)){ + otherReq = other; + } + }); + + if(otherReq == null) return; + + Lines.stroke(8f); + Lines.line(bridgeRegion, + req.drawx(), + req.drawy(), + otherReq.drawx(), + otherReq.drawy(), CapStyle.none, -tilesize / 2f); + Draw.rect(arrowRegion, (req.drawx() + otherReq.drawx()) / 2f, (req.drawy() + otherReq.drawy()) / 2f, + Angles.angle(req.drawx(), req.drawy(), otherReq.drawx(), otherReq.drawy())); + } + @Override public void playerPlaced(Tile tile){ Tile link = findLink(tile.x, tile.y); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java index c5ada69c89..04a8af5b9b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -36,6 +36,7 @@ public class MassDriver extends Block{ super(name); update = true; solid = true; + posConfig = true; configurable = true; hasItems = true; layer = Layer.turret; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java index b6c9c7831b..bf6cb7d436 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -1,10 +1,12 @@ package io.anuke.mindustry.world.blocks.distribution; import io.anuke.arc.*; +import io.anuke.arc.function.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; @@ -46,6 +48,11 @@ public class Sorter extends Block{ tile.entity().sortItem = content.item(value); } + @Override + public void drawRequestConfig(BuildRequest req, Eachable list){ + drawRequestConfigCenter(req, content.item(req.config), "center"); + } + @Override public void draw(Tile tile){ super.draw(tile); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 453b4032c5..1907329ee4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -33,53 +33,6 @@ public class PowerNode extends PowerBlock{ consumesPower = false; outputsPower = false; } -/* - @Remote(targets = Loc.both, called = Loc.server, forward = true) - public static void linkPowerNodes(Player player, Tile tile, Tile other){ - if(tile.entity == null || other == null || tile.entity.power == null || !((PowerNode)tile.block()).linkValid(tile, other) - || tile.entity.power.links.size >= ((PowerNode)tile.block()).maxNodes) return; - if(!Units.canInteract(player, tile)) return; - - TileEntity entity = tile.entity(); - - if(!entity.power.links.contains(other.pos())){ - entity.power.links.add(other.pos()); - } - - if(other.getTeamID() == tile.getTeamID()){ - - if(!other.entity.power.links.contains(tile.pos())){ - other.entity.power.links.add(tile.pos()); - } - } - - entity.power.graph.add(other.entity.power.graph); - } - - @Remote(targets = Loc.both, called = Loc.server, forward = true) - public static void unlinkPowerNodes(Player player, Tile tile, Tile other){ - if(tile.entity.power == null || other.entity == null || other.entity.power == null) return; - if(!Units.canInteract(player, tile)) return; - - TileEntity entity = tile.entity(); - - entity.power.links.removeValue(other.pos()); - other.entity.power.links.removeValue(tile.pos()); - - PowerGraph newgraph = new PowerGraph(); - - //reflow from this point, covering all tiles on this side - newgraph.reflow(tile); - - if(other.entity.power.graph != newgraph){ - //create new graph for other end - PowerGraph og = new PowerGraph(); - //reflow from other end - og.reflow(other); - } - } - - */ @Override public void configured(Tile tile, Player player, int value){ diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java index 20804daa28..2cd220d7b2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Fracker.java @@ -34,6 +34,11 @@ public class Fracker extends SolidPump{ topRegion = Core.atlas.find(name + "-top"); } + @Override + public boolean outputsItems(){ + return false; + } + @Override public void drawCracks(Tile tile){} diff --git a/core/src/io/anuke/mindustry/world/blocks/sandbox/ItemSource.java b/core/src/io/anuke/mindustry/world/blocks/sandbox/ItemSource.java index 8611eb18b7..aebc81da2a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/sandbox/ItemSource.java +++ b/core/src/io/anuke/mindustry/world/blocks/sandbox/ItemSource.java @@ -1,8 +1,10 @@ package io.anuke.mindustry.world.blocks.sandbox; import io.anuke.arc.*; +import io.anuke.arc.function.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.ui.layout.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; @@ -43,6 +45,11 @@ public class ItemSource extends Block{ bars.remove("items"); } + @Override + public void drawRequestConfig(BuildRequest req, Eachable list){ + drawRequestConfigCenter(req, content.item(req.config), "center"); + } + @Override public boolean outputsItems(){ return true; diff --git a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java index 26177da5a0..0aa787c79e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java +++ b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java @@ -2,11 +2,13 @@ package io.anuke.mindustry.world.blocks.sandbox; import io.anuke.arc.*; import io.anuke.arc.collection.*; +import io.anuke.arc.function.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.style.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; @@ -57,6 +59,11 @@ public class LiquidSource extends Block{ } } + @Override + public void drawRequestConfig(BuildRequest req, Eachable list){ + drawRequestConfigCenter(req, content.liquid(req.config), "center"); + } + @Override public void draw(Tile tile){ super.draw(tile); diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java index 9488337209..ceb9191948 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -1,9 +1,11 @@ package io.anuke.mindustry.world.blocks.storage; import io.anuke.arc.*; +import io.anuke.arc.function.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.ui.layout.*; +import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; @@ -28,6 +30,11 @@ public class Unloader extends Block{ configurable = true; } + @Override + public void drawRequestConfig(BuildRequest req, Eachable list){ + drawRequestConfigCenter(req, content.item(req.config), "unloader-center"); + } + @Override public boolean canDump(Tile tile, Tile to, Item item){ return !(to.block() instanceof StorageBlock); @@ -109,7 +116,7 @@ public class Unloader extends Block{ UnloaderEntity entity = tile.entity(); Draw.color(entity.sortItem == null ? Color.clear : entity.sortItem.color); - Fill.square(tile.worldx(), tile.worldy(), 1f); + Draw.rect("unloader-center", tile.worldx(), tile.worldy()); Draw.color(); } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index e97099f3ef..a35b74f619 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -22,10 +22,10 @@ import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.desktop.steam.*; import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.Version; -import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Net.*; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.ui.*; import java.io.*; @@ -249,28 +249,18 @@ public class DesktopLauncher extends ClientLauncher{ } @Override - public Array getExternalMaps(){ - return !steam ? super.getExternalMaps() : SVars.workshop.getMapFiles(); + public Array getWorkshopContent(Class type){ + return !steam ? super.getWorkshopContent(type) : SVars.workshop.getWorkshopFiles(type); } @Override - public Array getExternalMods(){ - return !steam ? super.getExternalMods() : SVars.workshop.getModFiles(); + public void viewListing(Publishable pub){ + SVars.workshop.viewListing(pub); } @Override - public void viewMapListing(Map map){ - viewListing(map.file.parent().name()); - } - - @Override - public void viewListing(String mapid){ - SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + mapid); - } - - @Override - public void viewMapListingInfo(Map map){ - SVars.workshop.viewMapListingInfo(map); + public void viewListingID(String id){ + SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + id); } @Override @@ -284,8 +274,8 @@ public class DesktopLauncher extends ClientLauncher{ } @Override - public void publishMap(Map map){ - SVars.workshop.publishMap(map); + public void publish(Publishable pub){ + SVars.workshop.publish(pub); } @Override diff --git a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java index 31f6029e44..b5b010bb9e 100644 --- a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java +++ b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java @@ -9,10 +9,11 @@ import io.anuke.arc.files.*; import io.anuke.arc.function.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.util.*; -import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.Mods.*; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.ui.dialogs.*; import static io.anuke.mindustry.Vars.*; @@ -20,10 +21,10 @@ import static io.anuke.mindustry.Vars.*; public class SWorkshop implements SteamUGCCallback{ public final SteamUGC ugc = new SteamUGC(this); - private Map lastMap; - private Array mapFiles; - private Array modFiles; + private ObjectMap, Array> workshopFiles = new ObjectMap<>(); private ObjectMap, SteamResult>> detailHandlers = new ObjectMap<>(); + private Array> itemHandlers = new Array<>(); + private ObjectMap updatedHandlers = new ObjectMap<>(); public SWorkshop(){ int items = ugc.getNumSubscribedItems(); @@ -36,71 +37,56 @@ public class SWorkshop implements SteamUGCCallback{ return new FileHandle(info.getFolder()); }).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]); - modFiles = folders.select(f -> f.child("mod.json").exists()); + workshopFiles.put(Map.class, folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(mapExtension)).map(f -> f.list()[0])); + workshopFiles.put(Schematic.class, folders.select(f -> f.list().length == 1 && f.list()[0].extension().equals(schematicExtension)).map(f -> f.list()[0])); + workshopFiles.put(LoadedMod.class, folders.select(f -> f.child("mod.json").exists())); - if(!mapFiles.isEmpty()){ + if(!workshopFiles.get(Map.class).isEmpty()){ SAchievement.downloadMapWorkshop.complete(); } - Log.info("Fetching {0} subscribed maps.", mapFiles.size); - Log.info("Fetching {0} subscribed mods.", modFiles.size); + workshopFiles.each((type, list) -> { + Log.info("Fetched content ({0}): {1}", type.getSimpleName(), list.size); + }); } - public Array getMapFiles(){ - return mapFiles; + public Array getWorkshopFiles(Class type){ + return workshopFiles.getOr(type, () -> new Array<>(0)); } - public Array getModFiles(){ - return modFiles; - } - - public void publishMap(Map map){ - if(map.tags.containsKey("steamid")){ - Log.info("Map already published, redirecting to ID."); - SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + map.tags.get("steamid")); + /** Publish a new item and submit an update for it. + * If it is already published, redirects to its page.*/ + public void publish(Publishable p){ + if(p.hasSteamID()){ + Log.info("Content already published, redirecting to ID."); + viewListing(p); return; } - //update author name when publishing - map.tags.put("author", SVars.net.friends.getPersonaName()); - ui.editor.editor.getTags().put("author", map.tags.get("author")); - ui.editor.save(); + if(!p.prePublish()){ + return; + } - FloatingDialog dialog = new FloatingDialog("$confirm"); - dialog.setFillParent(false); - dialog.cont.add("$map.publish.confirm").width(600f).wrap(); - dialog.addCloseButton(); - dialog.buttons.addImageTextButton("$eula", Icon.linkSmall, () -> { - SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement"); - }).size(210f, 64f); - - dialog.buttons.addImageTextButton("$ok", Icon.checkSmall, () -> { - this.lastMap = map; - ugc.createItem(SVars.steamID, WorkshopFileType.Community); - ui.loadfrag.show("$map.publishing"); - Log.info("Publish map " + map.name()); - dialog.hide(); - }).size(170f, 64f); - dialog.show(); + showPublish(id -> update(p, id, null)); } - public void viewMapListingInfo(Map map){ - String id = map.tags.get("steamid"); + /** Update an existing item with a changelog. */ + public void updateItem(Publishable p, String changelog){ + String id = p.getSteamID(); long handle = Strings.parseLong(id, -1); SteamPublishedFileID fid = new SteamPublishedFileID(handle); + update(p, fid, changelog); + } - Log.info("Requesting map listing view; id = " + id); + /** Fetches info for an item, checking to make sure that it exists.*/ + public void viewListing(Publishable p){ + long handle = Strings.parseLong(p.getSteamID(), -1); + SteamPublishedFileID id = new SteamPublishedFileID(handle); ui.loadfrag.show(); - SteamUGCQuery query = ugc.createQueryUGCDetailsRequest(fid); - Log.info("POST " + query); - - detailHandlers.put(query, (detailsList, result) -> { + query(ugc.createQueryUGCDetailsRequest(id), (detailsList, result) -> { ui.loadfrag.hide(); - Log.info("Map listing result: " + result + " " + detailsList); - if(result == SteamResult.OK){ SteamUGCDetails details = detailsList.first(); if(details.getResult() == SteamResult.OK){ @@ -112,54 +98,114 @@ public class SWorkshop implements SteamUGCCallback{ dialog.addCloseButton(); dialog.buttons.addImageTextButton("$view.workshop", Icon.linkSmall, () -> { - platform.viewListing(id); + viewListingID(id); dialog.hide(); }).size(210f, 64f); - dialog.buttons.addImageTextButton("$map.update", Icon.upgradeSmall, () -> { - new FloatingDialog("$map.update"){{ + dialog.buttons.addImageTextButton("$workshop.update", Icon.upgradeSmall, () -> { + new FloatingDialog("$workshop.update"){{ setFillParent(false); - cont.margin(10).add("$map.changelog").padRight(6f); + cont.margin(10).add("$changelog").padRight(6f); cont.row(); TextArea field = cont.addArea("", t -> {}).size(500f, 160f).get(); field.setMaxLength(400); buttons.defaults().size(120, 54).pad(4); buttons.addButton("$ok", () -> { - ui.loadfrag.show("$map.publishing"); - lastMap = map; - updateMap(map, details.getPublishedFileID(), field.getText().replace("\r", "\n")); + ui.loadfrag.show("$publishing"); + updateItem(p, field.getText().replace("\r", "\n")); dialog.hide(); hide(); - - Log.info("Update map " + map.name()); }); buttons.addButton("$cancel", this::hide); }}.show(); }).size(210f, 64f); dialog.show(); - }else{ SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + SteamNativeHandle.getNativeHandle(details.getPublishedFileID())); } }else if(details.getResult() == SteamResult.FileNotFound){ - //force-remove tags - ui.editor.editor.getTags().remove("steamid"); - map.tags.remove("steamid"); - ui.editor.save(); - - ui.showErrorMessage("$map.missing"); + p.removeSteamID(); + ui.showErrorMessage("$missing"); }else{ - ui.showErrorMessage(Core.bundle.format("map.load.error", result.name())); + ui.showErrorMessage(Core.bundle.format("workshop.error", result.name())); } }else{ - ui.showErrorMessage(Core.bundle.format("map.load.error", result.name())); + ui.showErrorMessage(Core.bundle.format("workshop.error", result.name())); } }); + } + void viewListingID(SteamPublishedFileID id){ + SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + SteamNativeHandle.getNativeHandle(id)); + } + + void update(Publishable p, SteamPublishedFileID id, String changelog){ + String sid = SteamNativeHandle.getNativeHandle(id) + ""; + + updateItem(id, h -> { + if(p.steamDescription() != null){ + ugc.setItemDescription(h, p.steamDescription()); + } + + Array tags = p.extraTags(); + tags.add(p.steamTag()); + + ugc.setItemTitle(h, p.steamTitle()); + ugc.setItemTags(h, tags.toArray(String.class)); + ugc.setItemPreview(h, p.createSteamPreview(sid).absolutePath()); + ugc.setItemContent(h, p.createSteamFolder(sid).absolutePath()); + if(changelog == null){ + ugc.setItemVisibility(h, PublishedFileVisibility.Private); + } + ugc.submitItemUpdate(h, changelog == null ? "" : changelog); + }, () -> p.addSteamID(sid)); + } + + void showPublish(Consumer published){ + FloatingDialog dialog = new FloatingDialog("$confirm"); + dialog.setFillParent(false); + dialog.cont.add("$publish.confirm").width(600f).wrap(); + dialog.addCloseButton(); + dialog.buttons.addImageTextButton("$eula", Icon.linkSmall, + () -> SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement")) + .size(210f, 64f); + + dialog.buttons.addImageTextButton("$ok", Icon.checkSmall, () -> { + ugc.createItem(SVars.steamID, WorkshopFileType.Community); + ui.loadfrag.show("$publishing"); + dialog.hide(); + itemHandlers.add(published); + }).size(170f, 64f); + dialog.show(); + } + + void query(SteamUGCQuery query, BiConsumer, SteamResult> handler){ + Log.info("POST QUERY " + query); + detailHandlers.put(query, handler); ugc.sendQueryUGCRequest(query); } + void updateItem(SteamPublishedFileID publishedFileID, Consumer tagger, Runnable updated){ + SteamUGCUpdateHandle h = ugc.startItemUpdate(SVars.steamID, publishedFileID); + + tagger.accept(h); + + ItemUpdateInfo info = new ItemUpdateInfo(); + + ui.loadfrag.setProgress(() -> { + ItemUpdateStatus status = ugc.getItemUpdateProgress(h, info); + ui.loadfrag.setText("$" + status.name().toLowerCase()); + if(status == ItemUpdateStatus.Invalid){ + ui.loadfrag.setText("$done"); + return 1f; + } + return (float)status.ordinal() / (float)ItemUpdateStatus.values().length; + }); + + updatedHandlers.put(publishedFileID, updated); + } + @Override public void onRequestUGCDetails(SteamUGCDetails details, SteamResult result){ @@ -167,7 +213,7 @@ public class SWorkshop implements SteamUGCCallback{ @Override public void onUGCQueryCompleted(SteamUGCQuery query, int numResultsReturned, int totalMatchingResults, boolean isCachedData, SteamResult result){ - Log.info("GET " + query); + Log.info("GET QUERY " + query); if(detailHandlers.containsKey(query)){ if(numResultsReturned > 0){ @@ -202,55 +248,15 @@ public class SWorkshop implements SteamUGCCallback{ @Override public void onCreateItem(SteamPublishedFileID publishedFileID, boolean needsToAcceptWLA, SteamResult result){ - if(lastMap == null){ - Log.err("No map to publish?"); - return; - } - - //SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + publishedFileID.toString()); - - Map map = lastMap; - Log.info("Create item {0} result {1} {2}", SteamNativeHandle.getNativeHandle(publishedFileID), result, needsToAcceptWLA); - - if(result == SteamResult.OK){ - updateMap(map, publishedFileID, ""); - }else{ - ui.showErrorMessage(Core.bundle.format("map.publish.error ", result.name())); - } - - lastMap = null; - } - - void updateMap(Map map, SteamPublishedFileID publishedFileID, String changelog){ - SteamUGCUpdateHandle h = ugc.startItemUpdate(SVars.steamID, publishedFileID); - - Gamemode mode = Gamemode.attack.valid(map) ? Gamemode.attack : Gamemode.survival; - FileHandle mapFile = tmpDirectory.child("map_" + publishedFileID.toString()).child("map.msav"); - lastMap.file.copyTo(mapFile); - - Log.info(mapFile.parent().absolutePath()); - Log.info(map.previewFile().absolutePath()); - - ugc.setItemTitle(h, map.name()); - ugc.setItemDescription(h, map.description()); - ugc.setItemTags(h, new String[]{"map", mode.name()}); - ugc.setItemVisibility(h, PublishedFileVisibility.Private); - ugc.setItemPreview(h, map.previewFile().absolutePath()); - ugc.setItemContent(h, mapFile.parent().absolutePath()); - ugc.addItemKeyValueTag(h, "mode", mode.name()); - ugc.submitItemUpdate(h, changelog); - - ItemUpdateInfo info = new ItemUpdateInfo(); - - ui.loadfrag.setProgress(() -> { - ItemUpdateStatus status = ugc.getItemUpdateProgress(h, info); - ui.loadfrag.setText("$" + status.name().toLowerCase()); - if(status == ItemUpdateStatus.Invalid){ - ui.loadfrag.setText("$done"); - return 1f; + if(!itemHandlers.isEmpty()){ + if(result == SteamResult.OK){ + itemHandlers.first().accept(publishedFileID); + }else{ + ui.showErrorMessage(Core.bundle.format("publish.error ", result.name())); } - return (float)status.ordinal() / (float)ItemUpdateStatus.values().length; - }); + + itemHandlers.remove(0); + } } @Override @@ -263,15 +269,12 @@ public class SWorkshop implements SteamUGCCallback{ if(needsToAcceptWLA){ SVars.net.friends.activateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement"); } - ui.editor.editor.getTags().put("steamid", SteamNativeHandle.getNativeHandle(publishedFileID) + ""); - try{ - ui.editor.save(); - }catch(Exception e){ - Log.err(e); + + if(updatedHandlers.containsKey(publishedFileID)){ + updatedHandlers.get(publishedFileID).run(); } - Events.fire(new MapPublishEvent()); }else{ - ui.showErrorMessage(Core.bundle.format("map.publish.error ", result.name())); + ui.showErrorMessage(Core.bundle.format("publish.error ", result.name())); } } diff --git a/gradle.properties b/gradle.properties index 4386fc3f98..c07c359326 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=983942d521c1e1714fac3ddfc54ee7248734b2ed +archash=e82f446a81abba5d6f712b9053c1b84ec9a73156