diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index 6f103ab2a0..573ad8a832 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -781,7 +781,7 @@ public class Blocks implements ContentList{ }}; copperWallLarge = new Wall("copper-wall-large"){{ - requirements(Category.defense, ItemStack.mult(copperWall.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(copperWall.requirements, 4)); health = 80 * 4 * wallHealthMultiplier; size = 2; }}; @@ -792,7 +792,7 @@ public class Blocks implements ContentList{ }}; titaniumWallLarge = new Wall("titanium-wall-large"){{ - requirements(Category.defense, ItemStack.mult(titaniumWall.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(titaniumWall.requirements, 4)); health = 110 * wallHealthMultiplier * 4; size = 2; }}; @@ -803,7 +803,7 @@ public class Blocks implements ContentList{ }}; thoriumWallLarge = new Wall("thorium-wall-large"){{ - requirements(Category.defense, ItemStack.mult(thoriumWall.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(thoriumWall.requirements, 4)); health = 200 * wallHealthMultiplier * 4; size = 2; }}; @@ -814,7 +814,7 @@ public class Blocks implements ContentList{ }}; phaseWallLarge = new DeflectorWall("phase-wall-large"){{ - requirements(Category.defense, ItemStack.mult(phaseWall.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(phaseWall.requirements, 4)); health = 150 * 4 * wallHealthMultiplier; size = 2; }}; @@ -825,7 +825,7 @@ public class Blocks implements ContentList{ }}; surgeWallLarge = new SurgeWall("surge-wall-large"){{ - requirements(Category.defense, ItemStack.mult(surgeWall.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(surgeWall.requirements, 4)); health = 230 * 4 * wallHealthMultiplier; size = 2; }}; @@ -836,7 +836,7 @@ public class Blocks implements ContentList{ }}; doorLarge = new Door("door-large"){{ - requirements(Category.defense, ItemStack.mult(door.buildRequirements, 4)); + requirements(Category.defense, ItemStack.mult(door.requirements, 4)); openfx = Fx.dooropenlarge; closefx = Fx.doorcloselarge; health = 100 * 4 * wallHealthMultiplier; diff --git a/core/src/io/anuke/mindustry/content/TechTree.java b/core/src/io/anuke/mindustry/content/TechTree.java index 7e5f7411bc..f22642e314 100644 --- a/core/src/io/anuke/mindustry/content/TechTree.java +++ b/core/src/io/anuke/mindustry/content/TechTree.java @@ -303,9 +303,9 @@ public class TechTree implements ContentList{ } private TechNode node(Block block, Runnable children){ - ItemStack[] requirements = new ItemStack[block.buildRequirements.length]; + ItemStack[] requirements = new ItemStack[block.requirements.length]; for(int i = 0; i < requirements.length; i++){ - requirements[i] = new ItemStack(block.buildRequirements[i].item, 30 + block.buildRequirements[i].amount * 6); + requirements[i] = new ItemStack(block.requirements[i].item, 30 + block.requirements[i].amount * 6); } return new TechNode(block, requirements, children); diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index cda4c8bda3..c302eb912d 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -58,37 +58,11 @@ public class ContentLoader{ list.load(); } - setupMapping(); - if(mods != null){ 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()]){ - if(c instanceof MappableContent){ - String name = ((MappableContent)c).name; - if(contentNameMap[type.ordinal()].containsKey(name)){ - throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + name + "')"); - } - contentNameMap[type.ordinal()].put(name, (MappableContent)c); - } - } - } - - //set up ID mapping + //check up ID mapping, make sure it's linear for(Array arr : contentMap){ for(int i = 0; i < arr.size; i++){ int id = arr.get(i).id; @@ -97,6 +71,8 @@ public class ContentLoader{ } } } + + loaded = true; } /** Logs content statistics.*/ @@ -125,6 +101,7 @@ public class ContentLoader{ for(ContentType type : ContentType.values()){ for(Content content : contentMap[type.ordinal()]){ + //TODO catch error and display it per mod callable.accept(content); } } @@ -154,6 +131,14 @@ public class ContentLoader{ public void handleContent(Content content){ contentMap[content.getContentType().ordinal()].add(content); + + } + + public void handleMappableContent(MappableContent content){ + if(contentNameMap[content.getContentType().ordinal()].containsKey(content.name)){ + throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')"); + } + contentNameMap[content.getContentType().ordinal()].put(content.name, content); } public void setTemporaryMapper(MappableContent[][] temporaryMapper){ diff --git a/core/src/io/anuke/mindustry/game/MappableContent.java b/core/src/io/anuke/mindustry/game/MappableContent.java index 3253975085..785113d2b8 100644 --- a/core/src/io/anuke/mindustry/game/MappableContent.java +++ b/core/src/io/anuke/mindustry/game/MappableContent.java @@ -1,10 +1,13 @@ package io.anuke.mindustry.game; +import io.anuke.mindustry.*; + public abstract class MappableContent extends Content{ public final String name; public MappableContent(String name){ this.name = name; + Vars.content.handleMappableContent(this); } @Override diff --git a/core/src/io/anuke/mindustry/mod/ContentParser.java b/core/src/io/anuke/mindustry/mod/ContentParser.java index fecd7dcca4..75728c884e 100644 --- a/core/src/io/anuke/mindustry/mod/ContentParser.java +++ b/core/src/io/anuke/mindustry/mod/ContentParser.java @@ -13,6 +13,7 @@ import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.*; +import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; @@ -24,6 +25,10 @@ public class ContentParser{ put(BulletType.class, (type, data) -> field(Bullets.class, data)); put(Effect.class, (type, data) -> field(Fx.class, data)); }}; + /** 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 LoadedMod currentMod; private Json parser = new Json(){ public T readValue(Class type, Class elementType, JsonValue jsonData){ @@ -33,7 +38,11 @@ public class ContentParser{ } if(Content.class.isAssignableFrom(type)){ - return (T)Vars.content.getByName(contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName())), jsonData.asString()); + ContentType ctype = contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName())); + String prefix = currentMod != null ? currentMod.name + "-" : ""; + T one = (T)Vars.content.getByName(ctype, prefix + jsonData.asString()); + if(one != null) return one; + return (T)Vars.content.getByName(ctype, jsonData.asString()); } } @@ -43,21 +52,37 @@ public class ContentParser{ private ObjectMap> parsers = ObjectMap.of( ContentType.block, (TypeParser)(mod, name, value) -> { - Class type = resolve(value.getString("type"), "io.anuke.mindustry.world", "io.anuke.mindustry.world.blocks", "io.anuke.mindustry.world.blocks.defense"); - Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name); - readFields(block, value, true); + //TODO generate dynamically instead of doing.. this + Class type = resolve(value.getString("type"), + "io.anuke.mindustry.world", + "io.anuke.mindustry.world.blocks", + "io.anuke.mindustry.world.blocks.defense", + "io.anuke.mindustry.world.blocks.defense.turrets", + "io.anuke.mindustry.world.blocks.distribution", + "io.anuke.mindustry.world.blocks.logic", + "io.anuke.mindustry.world.blocks.power", + "io.anuke.mindustry.world.blocks.production", + "io.anuke.mindustry.world.blocks.sandbox", + "io.anuke.mindustry.world.blocks.storage", + "io.anuke.mindustry.world.blocks.units" + ); - //make block visible - if(block.buildRequirements != null){ - block.buildVisibility = () -> true; - } + Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name); + read(() -> { + readFields(block, value, true); + + //make block visible + if(block.requirements != null){ + block.buildVisibility = () -> true; + } + }); return block; }, ContentType.unit, (TypeParser)(mod, name, value) -> { Class type = resolve(value.getString("type"), "io.anuke.mindustry.entities.type.base"); UnitType unit = new UnitType(mod + "-" + name, supply(type)); - readFields(unit, value, true); + read(() -> readFields(unit, value, true)); return unit; }, @@ -75,11 +100,19 @@ public class ContentParser{ }else{ item = constructor.get(mod + "-" + name); } - readFields(item, value); + read(() -> readFields(item, value)); return item; }; } + private void read(Runnable run){ + LoadedMod mod = currentMod; + reads.add(() -> { + this.currentMod = mod; + run.run(); + }); + } + private void init(){ for(ContentType type : ContentType.all){ Array arr = Vars.content.getBy(type); @@ -95,6 +128,11 @@ public class ContentParser{ } } + public void finishParsing(){ + reads.each(Runnable::run); + reads.clear(); + } + /** * Parses content from a json file. * @param name the name of the file without its extension @@ -102,7 +140,7 @@ public class ContentParser{ * @param type the type of content this is * @return the content that was parsed */ - public Content parse(String mod, String name, String json, ContentType type) throws Exception{ + public Content parse(LoadedMod mod, String name, String json, ContentType type) throws Exception{ if(contentTypes.isEmpty()){ init(); } @@ -112,7 +150,9 @@ public class ContentParser{ throw new SerializationException("No parsers for content type '" + type + "'"); } - Content c = parsers.get(type).parse(mod, name, value); + currentMod = mod; + Content c = parsers.get(type).parse(mod.name, name, value); + c.mod = mod; checkNulls(c); return c; } diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java index aa120aaacd..8292b23108 100644 --- a/core/src/io/anuke/mindustry/mod/Mods.java +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -44,8 +44,7 @@ public class Mods implements Loadable{ } /** @return the loaded mod found by class, or null if not found. */ - public @Nullable - LoadedMod getMod(Class type){ + public @Nullable LoadedMod getMod(Class type){ return loaded.find(l -> l.mod.getClass() == type); } @@ -76,26 +75,29 @@ public class Mods implements Loadable{ packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true); for(LoadedMod mod : loaded){ - try{ - int packed = 0; - for(FileHandle file : mod.root.child("sprites").list()){ - if(file.extension().equals("png")){ - 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); - pixmap.dispose(); - packed ++; - totalSprites ++; - } + int[] packed = {0}; + boolean[] failed = {false}; + mod.root.child("sprites").walk(file -> { + if(failed[0]) return; + if(file.extension().equals("png")){ + 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); + pixmap.dispose(); + packed[0] ++; + totalSprites ++; + }catch(IOException e){ + failed[0] = true; + Core.app.post(() -> { + Log.err("Error packing images for mod: {0}", mod.meta.name); + e.printStackTrace(); + if(!headless) ui.showException(e); + }); } } - Log.info("Packed {0} images for mod '{1}'.", packed, mod.meta.name); - }catch(IOException e){ - Log.err("Error packing images for mod: {0}", mod.meta.name); - e.printStackTrace(); - if(!headless) ui.showException(e); - } + }); + Log.info("Packed {0} images for mod '{1}'.", packed[0], mod.meta.name); } } @@ -197,7 +199,7 @@ public class Mods implements Loadable{ for(FileHandle file : folder.list()){ if(file.extension().equals("json")){ try{ - Content loaded = parser.parse(mod.name, file.nameWithoutExtension(), file.readString(), type); + Content loaded = parser.parse(mod, 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); @@ -209,6 +211,8 @@ public class Mods implements Loadable{ } } + parser.finishParsing(); + each(Mod::loadContent); } diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index 548ae1aee9..9424aa8651 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -80,7 +80,7 @@ public class PlacementFragment extends Fragment{ Block tryRecipe = tile.block(); if(tryRecipe.isVisible() && unlocked(tryRecipe)){ input.block = tryRecipe; - currentCategory = input.block.buildCategory; + currentCategory = input.block.category; return true; } } @@ -92,7 +92,7 @@ public class PlacementFragment extends Fragment{ for(KeyCode key : inputCatGrid){ if(Core.input.keyDown(key)){ input.block = getByCategory(Category.all[i]).first(); - currentCategory = input.block.buildCategory; + currentCategory = input.block.category; } i++; } @@ -147,7 +147,7 @@ public class PlacementFragment extends Fragment{ button.update(() -> { //color unplacable things gray TileEntity core = player.getClosestCore(); - Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.white : Color.gray; + Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.requirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.white : Color.gray; button.forEach(elem -> elem.setColor(color)); button.setChecked(control.input.block == block); }); @@ -205,7 +205,7 @@ public class PlacementFragment extends Fragment{ topTable.table(req -> { req.top().left(); - for(ItemStack stack : lastDisplay.buildRequirements){ + for(ItemStack stack : lastDisplay.requirements){ req.table(line -> { line.left(); line.addImage(stack.item.icon(Item.Icon.small)).size(8 * 2); @@ -296,7 +296,7 @@ public class PlacementFragment extends Fragment{ Array getByCategory(Category cat){ returnArray.clear(); for(Block block : content.blocks()){ - if(block.buildCategory == cat && block.isVisible()){ + if(block.category == cat && block.isVisible()){ returnArray.add(block); } } diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 15a2933b46..a78303f305 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -114,9 +114,9 @@ public class Block extends BlockStorage{ public float idleSoundVolume = 0.5f; /** Cost of constructing this block. */ - public ItemStack[] buildRequirements = new ItemStack[]{}; + public ItemStack[] requirements = new ItemStack[]{}; /** Category in place menu. */ - public Category buildCategory = Category.distribution; + public Category category = Category.distribution; /** Cost of building this block; do not modify directly! */ public float buildCost; /** Whether this block is visible and can currently be built. */ @@ -387,7 +387,7 @@ public class Block extends BlockStorage{ } buildCost = 0f; - for(ItemStack stack : buildRequirements){ + for(ItemStack stack : requirements){ buildCost += stack.amount * stack.item.cost; } @@ -493,7 +493,7 @@ public class Block extends BlockStorage{ stats.add(BlockStat.health, health, StatUnit.none); if(isBuildable()){ stats.add(BlockStat.buildTime, buildCost / 60, StatUnit.seconds); - stats.add(BlockStat.buildCost, new ItemListValue(false, buildRequirements)); + stats.add(BlockStat.buildCost, new ItemListValue(false, requirements)); } consumes.display(stats); @@ -772,11 +772,11 @@ public class Block extends BlockStorage{ /** Sets up requirements. Use only this method to set up requirements. */ protected void requirements(Category cat, BooleanProvider visible, ItemStack[] stacks){ - this.buildCategory = cat; - this.buildRequirements = stacks; + this.category = cat; + this.requirements = stacks; this.buildVisibility = visible; - Arrays.sort(buildRequirements, (a, b) -> Integer.compare(a.item.id, b.item.id)); + Arrays.sort(requirements, (a, b) -> Integer.compare(a.item.id, b.item.id)); } public enum Icon{ diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index d1d9a76708..1801222ff7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -196,8 +196,8 @@ public class BuildBlock extends Block{ float maxProgress = core == null ? amount : checkRequired(core.items, amount, false); - for(int i = 0; i < cblock.buildRequirements.length; i++){ - int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.buildRequirements[i].amount); + for(int i = 0; i < cblock.requirements.length; i++){ + int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount); accumulator[i] += Math.min(reqamount * maxProgress, reqamount - totalAccumulator[i] + 0.00001f); //add min amount progressed to the accumulator totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * maxProgress, reqamount); } @@ -221,7 +221,7 @@ public class BuildBlock extends Block{ float deconstructMultiplier = 0.5f; if(cblock != null){ - ItemStack[] requirements = cblock.buildRequirements; + ItemStack[] requirements = cblock.requirements; if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){ setDeconstruct(previous); } @@ -258,15 +258,15 @@ public class BuildBlock extends Block{ private float checkRequired(ItemModule inventory, float amount, boolean remove){ float maxProgress = amount; - for(int i = 0; i < cblock.buildRequirements.length; i++){ - int sclamount = Math.round(state.rules.buildCostMultiplier * cblock.buildRequirements[i].amount); + for(int i = 0; i < cblock.requirements.length; i++){ + int sclamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount); int required = (int)(accumulator[i]); //calculate items that are required now - if(inventory.get(cblock.buildRequirements[i].item) == 0 && sclamount != 0){ + if(inventory.get(cblock.requirements[i].item) == 0 && sclamount != 0){ maxProgress = 0f; }else if(required > 0){ //if this amount is positive... //calculate how many items it can actually use - int maxUse = Math.min(required, inventory.get(cblock.buildRequirements[i].item)); + int maxUse = Math.min(required, inventory.get(cblock.requirements[i].item)); //get this as a fraction float fraction = maxUse / (float)required; @@ -277,7 +277,7 @@ public class BuildBlock extends Block{ //remove stuff that is actually used if(remove){ - inventory.remove(cblock.buildRequirements[i].item, maxUse); + inventory.remove(cblock.requirements[i].item, maxUse); } } //else, no items are required yet, so just keep going @@ -293,8 +293,8 @@ public class BuildBlock extends Block{ public void setConstruct(Block previous, Block block){ this.cblock = block; this.previous = previous; - this.accumulator = new float[block.buildRequirements.length]; - this.totalAccumulator = new float[block.buildRequirements.length]; + this.accumulator = new float[block.requirements.length]; + this.totalAccumulator = new float[block.requirements.length]; this.buildCost = block.buildCost * state.rules.buildCostMultiplier; } @@ -303,8 +303,8 @@ public class BuildBlock extends Block{ this.progress = 1f; if(previous.buildCost >= 0.01f){ this.cblock = previous; - this.accumulator = new float[previous.buildRequirements.length]; - this.totalAccumulator = new float[previous.buildRequirements.length]; + this.accumulator = new float[previous.requirements.length]; + this.totalAccumulator = new float[previous.requirements.length]; this.buildCost = previous.buildCost * state.rules.buildCostMultiplier; }else{ this.buildCost = 20f; //default no-requirement build cost is 20 diff --git a/core/src/io/anuke/mindustry/world/blocks/OreBlock.java b/core/src/io/anuke/mindustry/world/blocks/OreBlock.java index b4ee4078f8..2c142cd6a1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/OreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/OreBlock.java @@ -1,7 +1,7 @@ package io.anuke.mindustry.world.blocks; -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; /**An overlay ore for a specific item type.*/ public class OreBlock extends OverlayFloor{ @@ -14,9 +14,27 @@ public class OreBlock extends OverlayFloor{ this.color.set(ore.color); } + /** For mod use only!*/ + public OreBlock(String name){ + super(name); + } + + public void setup(Item ore){ + this.localizedName = ore.localizedName(); + this.itemDrop = ore; + this.variants = 3; + this.color.set(ore.color); + } + @Override public void init(){ super.init(); + + if(itemDrop != null){ + setup(itemDrop); + }else{ + throw new IllegalArgumentException(name + " must have an item drop!"); + } } @Override