Modding improvements

This commit is contained in:
Anuken
2019-10-01 21:33:51 -04:00
parent 8ccdba5be2
commit 5b8c237a1e
10 changed files with 144 additions and 94 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Runnable> reads = new Array<>();
private LoadedMod currentMod;
private Json parser = new Json(){
public <T> T readValue(Class<T> 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<ContentType, TypeParser<?>> parsers = ObjectMap.of(
ContentType.block, (TypeParser<Block>)(mod, name, value) -> {
Class<Block> 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<? extends Block> 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<UnitType>)(mod, name, value) -> {
Class<BaseUnit> 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<Content> 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;
}

View File

@@ -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<? extends Mod> type){
public @Nullable LoadedMod getMod(Class<? extends Mod> 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);
}

View File

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

View File

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

View File

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

View File

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