Complete refactoring of workshop / Schematic+mod support

This commit is contained in:
Anuken
2019-10-19 18:53:27 -04:00
parent d0dc21a52c
commit 2586c53f0d
16 changed files with 441 additions and 216 deletions

View File

@@ -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,32 +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<FileHandle> getExternalMaps(){
return Array.with();
}
/** Steam: Return external workshop mods to be loaded.*/
default Array<FileHandle> getExternalMods(){
return Array.with();
}
/** Steam: Return external workshop schematics to be loaded.*/
default Array<FileHandle> getExternalSchematics(){
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<FileHandle> getWorkshopContent(Class<? extends Publishable> type){
return new Array<>(0);
}
/** Steam: Open workshop for maps.*/
default void openWorkshop(){}

View File

@@ -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();

View File

@@ -5,10 +5,13 @@ 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.*;
public class Schematic{
import static io.anuke.mindustry.Vars.*;
public class Schematic implements Publishable{
public final Array<Stile> tiles;
public StringMap tags;
public int width, height;
@@ -41,8 +44,54 @@ public class Schematic{
return tags.get("name", "unknown");
}
public boolean isWorkshop(){
return tags.containsKey("workshop");
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{

View File

@@ -58,7 +58,7 @@ public class Schematics implements Loadable{
loadFile(file);
}
platform.getExternalSchematics().each(this::loadFile);
platform.getWorkshopContent(Schematic.class).each(this::loadFile);
Core.app.post(() -> {
shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 2, maxSchematicSize + padding + 2);
@@ -69,7 +69,13 @@ public class Schematics implements Loadable{
if(!file.extension().equals(schematicExtension)) return;
try{
all.add(read(file));
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);
}
@@ -79,7 +85,30 @@ public class Schematics implements Loadable{
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();
@@ -113,11 +142,6 @@ public class Schematics implements Loadable{
buffer.beginDraw(Color.clear);
Draw.proj().setOrtho(0, buffer.getHeight(), buffer.getWidth(), -buffer.getHeight());
for(int x = 0; x < schematic.width + padding; x++){
for(int y = 0; y < schematic.height + padding; y++){
//Draw.rect("metal-floor", x * resolution + resolution/2f, y * resolution + resolution/2f, resolution, resolution);
}
}
Tmp.tr1.set(shadowBuffer.getTexture(), 0, 0, schematic.width + padding, schematic.height + padding);
Draw.color(0f, 0f, 0f, 1f);
@@ -127,8 +151,10 @@ public class Schematics implements Loadable{
Array<BuildRequest> 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);
@@ -148,7 +174,7 @@ public class Schematics implements Loadable{
Log.info("Time taken: {0}", Time.elapsed());
}
return previews.get(schematic).get(res).getTexture();
return previews.get(schematic).get(res);
}
/** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */

View File

@@ -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(){

View File

@@ -283,6 +283,11 @@ public class DesktopInput extends InputHandler{
schemY = rawCursorY;
}
if(Core.input.keyTap(Binding.clear_building)){
lastSchematic = null;
selectRequests.clear();
}
if(Core.input.keyRelease(Binding.schematic)){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);

View File

@@ -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<Map>{
public class Map implements Comparable<Map>, 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<Map>{
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<String> 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);

View File

@@ -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;

View File

@@ -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{" +

View File

@@ -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<String> 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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -91,12 +91,16 @@ public class SchematicsDialog extends FloatingDialog{
});
});
buttons.addImageButton(Icon.trash16Small, style, () -> {
ui.showConfirm("$confirm", "$schematic.delete.confirm", () -> {
schematics.remove(s);
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();
@@ -180,10 +184,9 @@ public class SchematicsDialog extends FloatingDialog{
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
if(steam){
t.addImageTextButton("$schematic.shareworkshop", Icon.wikiSmall, style, () -> {
}).marginLeft(12f);
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, () -> {