diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 8805c5f2fb..2a945de6d5 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -178,6 +178,7 @@ warning = Warning. confirm = Confirm delete = Delete view.workshop = View In Workshop +workshop.listing = Edit Workshop Listing ok = OK open = Open customize = Customize Rules @@ -215,7 +216,12 @@ map.nospawn.pvp = This map does not have any enemy cores for player to spawn int 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. 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): eula = Steam EULA map.publish = Map published. map.publishing = [accent]Publishing map... @@ -1071,7 +1077,7 @@ block.core-foundation.description = The second version of the core. Better armor block.core-nucleus.description = The third and final iteration of the core capsule. Extremely well armored. Stores massive amounts of resources. block.vault.description = Stores a large amount of items of each type. An unloader block can be used to retrieve items from the vault. block.container.description = Stores a small amount of items of each type. An unloader block can be used to retrieve items from the container. -block.unloader.description = Unloads items from a container, vault or core onto a conveyor or directly into an adjacent block. The type of item to be unloaded can be changed by tapping. +block.unloader.description = Unloads items from any nearby non-transportation block. The type of item to be unloaded can be changed by tapping. block.launch-pad.description = Launches batches of items without any need for a core launch. block.launch-pad-large.description = An improved version of the launch pad. Stores more items. Launches more frequently. block.duo.description = A small, cheap turret. Useful against ground units. diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index a689bbbd02..d7aa0ea55d 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -37,6 +37,10 @@ public interface Platform{ /** Steam: View a map listing on the workshop.*/ default void viewMapListing(String mapid){} + /** 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: Open workshop for maps.*/ default void openWorkshop(){} diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index abf08d50ef..517f503c7a 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -147,13 +147,13 @@ public class MapEditorDialog extends Dialog implements Disposable{ if(steam){ menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> { + Map map = save(); + if(editor.getTags().containsKey("steamid")){ - platform.viewMapListing(editor.getTags().get("steamid")); + platform.viewMapListingInfo(map); return; } - Map map = save(); - if(map == null) return; if(map.tags.get("description", "").length() < 4){ @@ -167,7 +167,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ } platform.publishMap(map); - }).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? "$view.workshop" : "$editor.publish.workshop")); + }).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/ui/Bar.java b/core/src/io/anuke/mindustry/ui/Bar.java index 3937a950ff..56c17befae 100644 --- a/core/src/io/anuke/mindustry/ui/Bar.java +++ b/core/src/io/anuke/mindustry/ui/Bar.java @@ -41,6 +41,10 @@ public class Bar extends Element{ } + public void reset(float value){ + this.value = lastValue = blink = value; + } + public void set(Supplier name, FloatProvider fraction, Color color){ this.fraction = fraction; this.lastValue = fraction.get(); diff --git a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java index ad29b50afa..12fbccc5a7 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/LoadingFragment.java @@ -38,6 +38,7 @@ public class LoadingFragment extends Fragment{ } public void setProgress(FloatProvider progress){ + bar.reset(0f); bar.visible(true); bar.set(() -> ((int)(progress.get() * 100) + "%"), progress, Pal.accent); } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index 8f671dbc04..a9f18600b7 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -35,7 +35,7 @@ import static io.anuke.mindustry.Vars.*; public class DesktopLauncher extends ClientLauncher{ public final static String discordID = "610508934456934412"; - boolean useDiscord = OS.is64Bit, showConsole = false; + boolean useDiscord = OS.is64Bit, showConsole = OS.getPropertyNotNull("user.name").equals("anuke"); public static void main(String[] arg){ try{ @@ -215,6 +215,11 @@ public class DesktopLauncher extends ClientLauncher{ SVars.net.friends.activateGameOverlayToWebPage("steam://url/CommunityFilePage/" + mapid); } + @Override + public void viewMapListingInfo(Map map){ + SVars.workshop.viewMapListingInfo(map); + } + @Override public NetProvider getNet(){ if(steam && SVars.net == null) SVars.net = new SNet(new ArcNetImpl()); diff --git a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java index 6ebf8792c9..915d585e77 100644 --- a/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java +++ b/desktop/src/io/anuke/mindustry/desktop/steam/SWorkshop.java @@ -6,6 +6,8 @@ import com.codedisaster.steamworks.SteamUGC.*; import io.anuke.arc.*; import io.anuke.arc.collection.*; 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.*; @@ -20,6 +22,7 @@ public class SWorkshop implements SteamUGCCallback{ private Map lastMap; private Array mapFiles; + private ObjectMap> detailHandlers = new ObjectMap<>(); public SWorkshop(){ int items = ugc.getNumSubscribedItems(); @@ -51,6 +54,8 @@ public class SWorkshop implements SteamUGCCallback{ //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(); FloatingDialog dialog = new FloatingDialog("$confirm"); dialog.setFillParent(false); @@ -70,9 +75,101 @@ public class SWorkshop implements SteamUGCCallback{ dialog.show(); } + public void viewMapListingInfo(Map map){ + + String id = map.tags.get("steamid"); + long handle = Strings.parseLong(id, -1); + SteamPublishedFileID fid = new SteamPublishedFileID(handle); + + Log.info("Requesting map listing view; id = " + id); + + ui.loadfrag.show(); + SteamUGCQuery query = ugc.createQueryUGCDetailsRequest(fid); + Log.info("POST " + query); + + detailHandlers.put(query, (details, result) -> { + ui.loadfrag.hide(); + + Log.info("Map listing result: " + result + " " + details.getResult() + " " + details.getFileName() + " " + details.getTitle()); + + if(result == SteamResult.OK){ + if(details.getResult() == SteamResult.OK){ + if(details.getOwnerID().equals(SVars.user.user.getSteamID())){ + + FloatingDialog dialog = new FloatingDialog("$editor.mapinfo"); + dialog.setFillParent(false); + dialog.cont.add("$map.menu").pad(20f); + dialog.addCloseButton(); + + dialog.buttons.addImageTextButton("$view.workshop", Icon.linkSmall, () -> { + platform.viewMapListing(id); + dialog.hide(); + }).size(210f, 64f); + + dialog.buttons.addImageTextButton("$map.update", Icon.upgradeSmall, () -> { + new FloatingDialog("$map.update"){{ + setFillParent(false); + cont.margin(10).add("$map.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")); + 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"); + }else{ + ui.showErrorMessage(Core.bundle.format("map.load.error", result.name())); + } + }else{ + ui.showErrorMessage(Core.bundle.format("map.load.error", result.name())); + } + }); + + ugc.sendQueryUGCRequest(query); + } + + @Override + public void onRequestUGCDetails(SteamUGCDetails details, SteamResult result){ + + } + @Override public void onUGCQueryCompleted(SteamUGCQuery query, int numResultsReturned, int totalMatchingResults, boolean isCachedData, SteamResult result){ + Log.info("GET " + query); + if(detailHandlers.containsKey(query)){ + if(numResultsReturned > 0){ + SteamUGCDetails details = new SteamUGCDetails(); + ugc.getQueryUGCResult(query, 0, details); + detailHandlers.get(query).accept(details, result); + }else{ + detailHandlers.get(query).accept(null, SteamResult.FileNotFound); + } + + detailHandlers.remove(query); + } } @Override @@ -90,11 +187,6 @@ public class SWorkshop implements SteamUGCCallback{ Log.info("Item unsubscribed from {0}", info.getFolder()); } - @Override - public void onRequestUGCDetails(SteamUGCDetails details, SteamResult result){ - - } - @Override public void onCreateItem(SteamPublishedFileID publishedFileID, boolean needsToAcceptWLA, SteamResult result){ if(lastMap == null){ @@ -108,35 +200,7 @@ public class SWorkshop implements SteamUGCCallback{ Log.info("Create item {0} result {1} {2}", SteamNativeHandle.getNativeHandle(publishedFileID), result, needsToAcceptWLA); if(result == SteamResult.OK){ - 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, "Map created"); - - 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; - }); + updateMap(map, publishedFileID, ""); }else{ ui.showErrorMessage(Core.bundle.format("map.publish.error ", result.name())); } @@ -144,6 +208,38 @@ public class SWorkshop implements SteamUGCCallback{ 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; + } + return (float)status.ordinal() / (float)ItemUpdateStatus.values().length; + }); + } + @Override public void onSubmitItemUpdate(SteamPublishedFileID publishedFileID, boolean needsToAcceptWLA, SteamResult result){ ui.loadfrag.hide();