mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-01-27 16:09:57 +07:00
JSON block, item loading
This commit is contained in:
parent
9c175ac893
commit
f17e46015a
@ -44,6 +44,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
assets.load("sprites/error.png", Texture.class);
|
||||
atlas = TextureAtlas.blankAtlas();
|
||||
Vars.net = new Net(platform.getNet());
|
||||
Vars.mods = new Mods();
|
||||
|
||||
UI.loadSystemCursors();
|
||||
|
||||
@ -55,12 +56,6 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
atlas = (TextureAtlas)t;
|
||||
};
|
||||
|
||||
if(!mods.all().isEmpty()){
|
||||
assets.loadRun("mods", Mods.class, () -> {
|
||||
mods.packSprites();
|
||||
});
|
||||
}
|
||||
|
||||
assets.loadRun("maps", Map.class, () -> maps.loadPreviews());
|
||||
|
||||
Musics.load();
|
||||
@ -69,8 +64,6 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
assets.loadRun("contentcreate", Content.class, () -> {
|
||||
content.createContent();
|
||||
content.loadColors();
|
||||
|
||||
mods.loadContent();
|
||||
});
|
||||
|
||||
add(logic = new Logic());
|
||||
@ -80,6 +73,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
add(netServer = new NetServer());
|
||||
add(netClient = new NetClient());
|
||||
|
||||
assets.load(mods);
|
||||
|
||||
assets.loadRun("contentinit", ContentLoader.class, () -> {
|
||||
content.init();
|
||||
content.load();
|
||||
|
@ -25,7 +25,7 @@ import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
|
||||
import static io.anuke.arc.Core.settings;
|
||||
import static io.anuke.arc.Core.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Vars implements Loadable{
|
||||
@ -195,7 +195,9 @@ public class Vars implements Loadable{
|
||||
Version.init();
|
||||
|
||||
filet = new FileTree();
|
||||
mods = new Mods();
|
||||
if(mods == null){
|
||||
mods = new Mods();
|
||||
}
|
||||
content = new ContentLoader();
|
||||
loops = new LoopControl();
|
||||
defaultWaves = new DefaultWaves();
|
||||
|
@ -11,6 +11,7 @@ import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.world.*;
|
||||
|
||||
import static io.anuke.arc.Core.files;
|
||||
import static io.anuke.mindustry.Vars.mods;
|
||||
|
||||
/**
|
||||
* Loads all game content.
|
||||
@ -57,6 +58,21 @@ public class ContentLoader{
|
||||
list.load();
|
||||
}
|
||||
|
||||
setupMapping();
|
||||
|
||||
mods.loadContent();
|
||||
|
||||
setupMapping();
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
private void setupMapping(){
|
||||
|
||||
for(ContentType type : ContentType.values()){
|
||||
contentNameMap[type.ordinal()].clear();
|
||||
}
|
||||
|
||||
for(ContentType type : ContentType.values()){
|
||||
|
||||
for(Content c : contentMap[type.ordinal()]){
|
||||
@ -79,8 +95,6 @@ public class ContentLoader{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
/** Logs content statistics.*/
|
||||
|
@ -1,16 +1,61 @@
|
||||
package io.anuke.mindustry.mod;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.reflect.*;
|
||||
import io.anuke.arc.util.serialization.*;
|
||||
import io.anuke.arc.util.serialization.Json.*;
|
||||
import io.anuke.mindustry.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.world.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ContentParser{
|
||||
private Json parser = new Json();
|
||||
private ObjectMap<ContentType, TypeParser<?>> parsers = ObjectMap.of(
|
||||
private static final boolean ignoreUnknownFields = true;
|
||||
private ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
|
||||
|
||||
private Json parser = new Json(){
|
||||
public <T> T readValue(Class<T> type, Class elementType, JsonValue jsonData){
|
||||
if(type != null && Content.class.isAssignableFrom(type)){
|
||||
return (T)Vars.content.getByName(contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName())), jsonData.asString());
|
||||
}
|
||||
return super.readValue(type, elementType, jsonData);
|
||||
}
|
||||
};
|
||||
|
||||
private ObjectMap<ContentType, TypeParser<?>> parsers = ObjectMap.of(
|
||||
ContentType.block, (TypeParser<Block>)(mod, name, value) -> {
|
||||
String clas = value.getString("type");
|
||||
Class<Block> type = resolve("io.anuke.mindustry.world." + clas, "io.anuke.mindustry.world.blocks." + clas, "io.anuke.mindustry.world.blocks.defense" + clas);
|
||||
Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name);
|
||||
value.remove("type");
|
||||
readFields(block, value);
|
||||
|
||||
//make block visible
|
||||
if(block.buildRequirements != null){
|
||||
block.buildVisibility = () -> true;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
);
|
||||
|
||||
private void init(){
|
||||
for(ContentType type : ContentType.all){
|
||||
Array<Content> arr = Vars.content.getBy(type);
|
||||
if(!arr.isEmpty()){
|
||||
Class<?> c = arr.first().getClass();
|
||||
//get base content class, skipping intermediates
|
||||
while(!(c.getSuperclass() == Content.class || c.getSuperclass() == UnlockableContent.class || c.getSuperclass() == UnlockableContent.class)){
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
|
||||
contentTypes.put(c, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses content from a json file.
|
||||
* @param name the name of the file without its extension
|
||||
@ -18,12 +63,64 @@ public class ContentParser{
|
||||
* @param type the type of content this is
|
||||
* @return the content that was parsed
|
||||
*/
|
||||
public Content parse(String name, String json, ContentType type) throws Exception{
|
||||
public Content parse(String mod, String name, String json, ContentType type) throws Exception{
|
||||
if(contentTypes.isEmpty()){
|
||||
init();
|
||||
}
|
||||
|
||||
JsonValue value = parser.fromJson(null, json);
|
||||
if(!parsers.containsKey(type)){
|
||||
throw new SerializationException("No parsers for content type '" + type + "'");
|
||||
}
|
||||
|
||||
return parsers.get(type).parse(name, value);
|
||||
return parsers.get(type).parse(mod, name, value);
|
||||
}
|
||||
|
||||
private void readFields(Object object, JsonValue jsonMap){
|
||||
Class type = object.getClass();
|
||||
ObjectMap<String, FieldMetadata> fields = parser.getFields(type);
|
||||
for(JsonValue child = jsonMap.child; child != null; child = child.next){
|
||||
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
|
||||
if(metadata == null){
|
||||
if(ignoreUnknownFields){
|
||||
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
|
||||
continue;
|
||||
}else{
|
||||
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
|
||||
ex.addTrace(child.trace());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
Field field = metadata.field;
|
||||
try{
|
||||
field.set(object, parser.readValue(field.getType(), metadata.elementType, child));
|
||||
}catch(ReflectionException ex){
|
||||
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex);
|
||||
}catch(SerializationException ex){
|
||||
ex.addTrace(field.getName() + " (" + type.getName() + ")");
|
||||
throw ex;
|
||||
}catch(RuntimeException runtimeEx){
|
||||
SerializationException ex = new SerializationException(runtimeEx);
|
||||
ex.addTrace(child.trace());
|
||||
ex.addTrace(field.getName() + " (" + type.getName() + ")");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Tries to resolve a class from a list of potential class names. */
|
||||
private <T> Class<T> resolve(String... potentials) throws Exception{
|
||||
for(String type : potentials){
|
||||
try{
|
||||
return (Class<T>)Class.forName(type);
|
||||
}catch(Exception ignored){
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Type not found: " + potentials[0]);
|
||||
}
|
||||
|
||||
public interface TypeParser<T extends Content>{
|
||||
T parse(String mod, String name, JsonValue value) throws Exception;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.anuke.mindustry.mod;
|
||||
|
||||
import io.anuke.annotations.Annotations.*;
|
||||
import io.anuke.arc.*;
|
||||
import io.anuke.arc.assets.*;
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.*;
|
||||
import io.anuke.arc.function.*;
|
||||
@ -20,12 +21,15 @@ import java.net.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class Mods{
|
||||
public class Mods implements Loadable{
|
||||
private Json json = new Json();
|
||||
private ContentParser parser = new ContentParser();
|
||||
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>();
|
||||
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites");
|
||||
|
||||
private int totalSprites;
|
||||
private PixmapPacker packer;
|
||||
|
||||
private Array<LoadedMod> loaded = new Array<>();
|
||||
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
|
||||
private boolean requiresRestart;
|
||||
@ -64,10 +68,11 @@ public class Mods{
|
||||
}
|
||||
|
||||
/** Repacks all in-game sprites. */
|
||||
public void packSprites(){
|
||||
int total = 0;
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
if(loaded.isEmpty()) return;
|
||||
|
||||
PixmapPacker packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
|
||||
packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
|
||||
for(LoadedMod mod : loaded){
|
||||
try{
|
||||
int packed = 0;
|
||||
@ -76,10 +81,10 @@ public class Mods{
|
||||
try(InputStream stream = file.read()){
|
||||
byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512));
|
||||
Pixmap pixmap = new Pixmap(bytes, 0, bytes.length);
|
||||
packer.pack(mod.name + ":" + file.nameWithoutExtension(), pixmap);
|
||||
packer.pack(mod.name + "-" + file.nameWithoutExtension(), pixmap);
|
||||
pixmap.dispose();
|
||||
packed ++;
|
||||
total ++;
|
||||
totalSprites ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,25 +95,28 @@ public class Mods{
|
||||
if(!headless) ui.showException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//only pack if there's something to be packed
|
||||
//TODO is disposing necessary/safe?
|
||||
if(total > 0){
|
||||
Core.app.post(() -> {
|
||||
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest;
|
||||
@Override
|
||||
public void loadSync(){
|
||||
if(packer == null) return;
|
||||
|
||||
packer.getPages().each(page -> page.updateTexture(filter, filter, false));
|
||||
packer.getPages().each(page -> page.getRects().each((name, rect) -> Core.atlas.addRegion(name, page.getTexture(), (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height)));
|
||||
packer.dispose();
|
||||
});
|
||||
}else{
|
||||
packer.dispose();
|
||||
if(totalSprites > 0){
|
||||
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest;
|
||||
packer.getPages().each(page -> page.updateTexture(filter, filter, false));
|
||||
packer.getPages().each(page -> page.getRects().each((name, rect) -> Core.atlas.addRegion(name, page.getTexture(), (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height)));
|
||||
}
|
||||
|
||||
packer.dispose();
|
||||
}
|
||||
|
||||
/** Removes a mod file and marks it for requiring a restart. */
|
||||
public void removeMod(LoadedMod mod){
|
||||
mod.file.delete();
|
||||
if(mod.file.isDirectory()){
|
||||
mod.file.deleteDirectory();
|
||||
}else{
|
||||
mod.file.delete();
|
||||
}
|
||||
loaded.remove(mod);
|
||||
requiresRestart = true;
|
||||
}
|
||||
@ -120,7 +128,7 @@ public class Mods{
|
||||
/** Loads all mods from the folder, but does call any methods on them.*/
|
||||
public void load(){
|
||||
for(FileHandle file : modDirectory.list()){
|
||||
if(!file.extension().equals("jar") && !file.extension().equals("zip")) continue;
|
||||
if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && file.child("mod.json").exists())) continue;
|
||||
|
||||
try{
|
||||
loaded.add(loadMod(file));
|
||||
@ -178,13 +186,13 @@ public class Mods{
|
||||
if(mod.root.child("content").exists()){
|
||||
FileHandle contentRoot = mod.root.child("content");
|
||||
for(ContentType type : ContentType.all){
|
||||
FileHandle folder = contentRoot.child(type.name());
|
||||
FileHandle folder = contentRoot.child(type.name() + "s");
|
||||
if(folder.exists()){
|
||||
for(FileHandle file : folder.list()){
|
||||
if(file.extension().equals("json")){
|
||||
try{
|
||||
Content loaded = parser.parse(file.nameWithoutExtension(), file.readString(), type);
|
||||
Log.info("[{0}] Loaded '{1}'", loaded, mod.meta.name);
|
||||
Content loaded = parser.parse(mod.name, file.nameWithoutExtension(), file.readString(), type);
|
||||
Log.info("[{0}] Loaded '{1}'.", mod.meta.name, loaded);
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
|
||||
}
|
||||
@ -206,13 +214,14 @@ public class Mods{
|
||||
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
|
||||
}
|
||||
|
||||
/** Loads a mod file+meta, but does not add it to the list. */
|
||||
private LoadedMod loadMod(FileHandle jar) throws Exception{
|
||||
FileHandle zip = new ZipFileHandle(jar);
|
||||
/** Loads a mod file+meta, but does not add it to the list.
|
||||
* Note that directories can be loaded as mods.*/
|
||||
private LoadedMod loadMod(FileHandle sourceFile) throws Exception{
|
||||
FileHandle zip = sourceFile.isDirectory() ? sourceFile : new ZipFileHandle(sourceFile);
|
||||
|
||||
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
|
||||
if(!metaf.exists()){
|
||||
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", jar);
|
||||
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", sourceFile);
|
||||
throw new IllegalArgumentException("No mod.json found.");
|
||||
}
|
||||
|
||||
@ -228,7 +237,7 @@ public class Mods{
|
||||
throw new IllegalArgumentException("This mod is not compatible with " + (ios ? "iOS" : "Android") + ".");
|
||||
}
|
||||
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
|
||||
Class<?> main = classLoader.loadClass(mainClass);
|
||||
metas.put(main, meta);
|
||||
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
|
||||
@ -236,14 +245,14 @@ public class Mods{
|
||||
mainMod = null;
|
||||
}
|
||||
|
||||
return new LoadedMod(jar, zip, mainMod, meta);
|
||||
return new LoadedMod(sourceFile, zip, mainMod, meta);
|
||||
}
|
||||
|
||||
/** Represents a plugin that has been loaded from a jar file.*/
|
||||
public static class LoadedMod{
|
||||
/** The location of this mod's zip file on the disk. */
|
||||
/** 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. */
|
||||
/** 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. */
|
||||
public final FileHandle root;
|
||||
/** The mod's main class; may be null. */
|
||||
public final @Nullable Mod mod;
|
||||
@ -260,7 +269,7 @@ public class Mods{
|
||||
this.file = file;
|
||||
this.mod = mod;
|
||||
this.meta = meta;
|
||||
this.name = Strings.camelize(meta.name);
|
||||
this.name = meta.name.toLowerCase().replace(" ", "-");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
package io.anuke.mindustry.mod;
|
||||
|
||||
import io.anuke.arc.util.serialization.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
|
||||
public abstract class TypeParser<T extends Content>{
|
||||
public abstract T parse(String name, JsonValue value);
|
||||
}
|
@ -142,8 +142,7 @@ public class CrashSender{
|
||||
private static void ex(Runnable r){
|
||||
try{
|
||||
r.run();
|
||||
}catch(Throwable t){
|
||||
t.printStackTrace();
|
||||
}catch(Throwable ignored){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user