diff --git a/src/main/java/gregtech/GregTechVersion.java b/src/main/java/gregtech/GregTechVersion.java index 57adc37c3e..8556f025fa 100644 --- a/src/main/java/gregtech/GregTechVersion.java +++ b/src/main/java/gregtech/GregTechVersion.java @@ -1,5 +1,7 @@ package gregtech; +import gregtech.api.util.Version; + public final class GregTechVersion { public static final int MAJOR = 1; @@ -10,6 +12,8 @@ public final class GregTechVersion { //This number is incremented every build, and never reset. Should always be 0 in the repo code. public static final int BUILD = 0; + public static final Version VERSION = new Version(MAJOR, MINOR, REVISION, BUILD); + private GregTechVersion() { } } diff --git a/src/main/java/gregtech/api/unification/material/Materials.java b/src/main/java/gregtech/api/unification/material/Materials.java index 44a3c4eaa0..9bed45c8aa 100644 --- a/src/main/java/gregtech/api/unification/material/Materials.java +++ b/src/main/java/gregtech/api/unification/material/Materials.java @@ -29,7 +29,7 @@ public static void register() { private static final long EXT_METAL = STD_METAL | GENERATE_ROD | GENERATE_BOLT_SCREW | GENERATE_LONG_ROD; private static final long EXT2_METAL = EXT_METAL | GENERATE_GEAR | GENERATE_FOIL | GENERATE_FINE_WIRE; - public static MarkerMaterial _NULL = new MarkerMaterial("_null"); + public static final MarkerMaterial _NULL = new MarkerMaterial("_null"); /** * Direct Elements diff --git a/src/main/java/gregtech/api/util/Version.java b/src/main/java/gregtech/api/util/Version.java new file mode 100644 index 0000000000..d018d0ce0a --- /dev/null +++ b/src/main/java/gregtech/api/util/Version.java @@ -0,0 +1,71 @@ +package gregtech.api.util; + +import java.util.Arrays; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class Version implements Comparable { + + private final int[] nums; + + public Version(int... nums) { + if (nums.length == 0) { + throw new IllegalArgumentException("Must be at least one version number!"); + } + for (int num : nums) { + if (num < 0) { + throw new IllegalArgumentException("Version numbers must be positive!"); + } + } + this.nums = nums; + } + + public static Version parse(String vStr) { + return new Version(Arrays.stream(vStr.split(Pattern.quote("."))).mapToInt(Integer::parseInt).toArray()); + } + + public int getNumber(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index must be nonnegative!"); + } + return index < nums.length ? nums[index] : 0; + } + + @Override + public int compareTo(Version o) { + int numBound = Math.max(nums.length, o.nums.length); + for (int i = 0; i < numBound; i++) { + int cmp = Integer.compare(getNumber(i), o.getNumber(i)); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Version && compareTo((Version) obj) == 0; + } + + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < nums.length; i++) { + hash ^= Integer.rotateLeft(nums[i], i * 7); + } + return hash; + } + + @Override + public String toString() { + return toString(nums.length); + } + + public String toString(int sigPlaces) { + return Arrays.stream(nums, 0, Math.min(sigPlaces, nums.length)) + .mapToObj(Integer::toString) + .collect(Collectors.joining(".")); + } + +} diff --git a/src/main/java/gregtech/common/CommonProxy.java b/src/main/java/gregtech/common/CommonProxy.java index 0056119f7c..6d0264a070 100644 --- a/src/main/java/gregtech/common/CommonProxy.java +++ b/src/main/java/gregtech/common/CommonProxy.java @@ -13,6 +13,7 @@ import gregtech.common.blocks.wood.BlockGregLeaves; import gregtech.common.blocks.wood.BlockGregLog; import gregtech.common.blocks.wood.BlockGregSapling; +import gregtech.common.datafix.GregTechDataFixers; import gregtech.common.items.MetaItems; import gregtech.common.items.potions.PotionFluids; import gregtech.common.pipelike.cable.ItemBlockCable; @@ -235,12 +236,11 @@ public void onPreLoad() { } public void onLoad() { - + GregTechDataFixers.init(); } public void onPostLoad() { WoodMachineRecipes.postInit(); } - -} \ No newline at end of file +} diff --git a/src/main/java/gregtech/common/asm/CompoundDataFixerGetVersionVisitor.java b/src/main/java/gregtech/common/asm/CompoundDataFixerGetVersionVisitor.java new file mode 100644 index 0000000000..b60787924a --- /dev/null +++ b/src/main/java/gregtech/common/asm/CompoundDataFixerGetVersionVisitor.java @@ -0,0 +1,38 @@ +package gregtech.common.asm; + +import gregtech.common.asm.util.ObfMapping; +import gregtech.common.asm.util.SafeMethodVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class CompoundDataFixerGetVersionVisitor extends SafeMethodVisitor { + + public static final String TARGET_CLASS_NAME = "net/minecraftforge/common/util/CompoundDataFixer$1"; + public static final ObfMapping TARGET_METHOD = new ObfMapping(TARGET_CLASS_NAME, "getVersion", "(Ljava/lang/String;)I"); + + private static final String WORLD_DATA_HOOKS_OWNER = "gregtech/common/datafix/fixes/metablockid/WorldDataHooks"; + private static final String WORLD_DATA_HOOKS_METHOD_NAME = "getFallbackModVersion"; + private static final String WORLD_DATA_HOOKS_SIGNATURE = "(Ljava/lang/String;)I"; + + public CompoundDataFixerGetVersionVisitor(MethodVisitor mv) { + super(Opcodes.ASM5, mv); + } + + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.ICONST_M1) { + markPatchedSuccessfully(); + super.visitVarInsn(Opcodes.ALOAD, 1); + super.visitMethodInsn(Opcodes.INVOKESTATIC, WORLD_DATA_HOOKS_OWNER, + WORLD_DATA_HOOKS_METHOD_NAME, WORLD_DATA_HOOKS_SIGNATURE, false); + } else { + super.visitInsn(opcode); + } + } + + @Override + protected String getInjectTargetString() { + return String.format("Patch target: %s; (point not found)", TARGET_METHOD); + } + +} diff --git a/src/main/java/gregtech/common/asm/GTCETransformer.java b/src/main/java/gregtech/common/asm/GTCETransformer.java index 1dec825df8..5a72966b18 100644 --- a/src/main/java/gregtech/common/asm/GTCETransformer.java +++ b/src/main/java/gregtech/common/asm/GTCETransformer.java @@ -1,12 +1,11 @@ package gregtech.common.asm; +import gregtech.common.asm.util.TargetClassVisitor; import net.minecraft.launchwrapper.IClassTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; -import gregtech.common.asm.util.*; - public class GTCETransformer implements IClassTransformer, Opcodes { @Override @@ -36,6 +35,18 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) classReader.accept(new TargetClassVisitor(classWriter, JEIVisitor.TARGET_METHOD, JEIVisitor::new), 0); return classWriter.toByteArray(); } + if (internalName.equals(SaveFormatOldLoadVisitor.TARGET_CLASS_NAME)) { + ClassReader classReader = new ClassReader(basicClass); + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + classReader.accept(new TargetClassVisitor(classWriter, SaveFormatOldLoadVisitor.TARGET_METHOD, SaveFormatOldLoadVisitor::new), 0); + return classWriter.toByteArray(); + } + if (internalName.equals(CompoundDataFixerGetVersionVisitor.TARGET_CLASS_NAME)) { + ClassReader classReader = new ClassReader(basicClass); + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + classReader.accept(new TargetClassVisitor(classWriter, CompoundDataFixerGetVersionVisitor.TARGET_METHOD, CompoundDataFixerGetVersionVisitor::new), 0); + return classWriter.toByteArray(); + } return basicClass; } } diff --git a/src/main/java/gregtech/common/asm/SaveFormatOldLoadVisitor.java b/src/main/java/gregtech/common/asm/SaveFormatOldLoadVisitor.java new file mode 100644 index 0000000000..843e632473 --- /dev/null +++ b/src/main/java/gregtech/common/asm/SaveFormatOldLoadVisitor.java @@ -0,0 +1,59 @@ +package gregtech.common.asm; + +import gregtech.common.asm.util.ObfMapping; +import gregtech.common.asm.util.SafeMethodVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class SaveFormatOldLoadVisitor extends SafeMethodVisitor { + + public static final String TARGET_CLASS_NAME = "net/minecraft/world/storage/SaveFormatOld"; + public static final ObfMapping TARGET_METHOD = new ObfMapping(TARGET_CLASS_NAME, "loadAndFix", // forge method + "(Ljava/io/File;Lnet/minecraft/util/datafix/DataFixer;Lnet/minecraft/world/storage/SaveHandler;)Lnet/minecraft/world/storage/WorldInfo;"); + + private static final String LOAD_COMPRESSED_OWNER = "net/minecraft/nbt/CompressedStreamTools"; + private static final ObfMapping LOAD_COMPRESSED_METHOD = new ObfMapping(LOAD_COMPRESSED_OWNER, "func_74796_a", + "(Ljava/io/InputStream;)Lnet/minecraft/nbt/NBTTagCompound;").toRuntime(); + + private static final String WORLD_DATA_HOOKS_OWNER = "gregtech/common/datafix/fixes/metablockid/WorldDataHooks"; + private static final String WORLD_DATA_HOOKS_METHOD_NAME = "onWorldLoad"; + private static final String WORLD_DATA_HOOKS_SIGNATURE = "(Lnet/minecraft/world/storage/SaveHandler;Lnet/minecraft/nbt/NBTTagCompound;)V"; + + private State state = State.WAITING_FOR_READ; + + public SaveFormatOldLoadVisitor(MethodVisitor mv) { + super(Opcodes.ASM5, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + if (state == State.WAITING_FOR_READ && opcode == Opcodes.INVOKESTATIC + && owner.equals(LOAD_COMPRESSED_OWNER) && LOAD_COMPRESSED_METHOD.matches(name, desc)) { + state = State.WAITING_FOR_VAR; + } + } + + @Override + public void visitVarInsn(int opcode, int var) { + super.visitVarInsn(opcode, var); + if (state == State.WAITING_FOR_VAR && opcode == Opcodes.ASTORE) { + state = State.DONE; + markPatchedSuccessfully(); + super.visitVarInsn(Opcodes.ALOAD, 2); + super.visitVarInsn(Opcodes.ALOAD, var); + super.visitMethodInsn(Opcodes.INVOKESTATIC, WORLD_DATA_HOOKS_OWNER, + WORLD_DATA_HOOKS_METHOD_NAME, WORLD_DATA_HOOKS_SIGNATURE, false); + } + } + + @Override + protected String getInjectTargetString() { + return String.format("Patch target: %s; (point not found)", TARGET_METHOD); + } + + private enum State { + WAITING_FOR_READ, WAITING_FOR_VAR, DONE + } + +} diff --git a/src/main/java/gregtech/common/blocks/MetaBlocks.java b/src/main/java/gregtech/common/blocks/MetaBlocks.java index caa5782b59..cb8d5e8964 100644 --- a/src/main/java/gregtech/common/blocks/MetaBlocks.java +++ b/src/main/java/gregtech/common/blocks/MetaBlocks.java @@ -1,6 +1,7 @@ package gregtech.common.blocks; import com.google.common.collect.ImmutableMap; +import gnu.trove.map.TIntObjectMap; import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.block.machines.BlockMachine; @@ -168,10 +169,13 @@ public static void init() { StoneType.init(); - createGeneratedBlock(material -> material instanceof DustMaterial && - !OrePrefix.block.isIgnored(material), MetaBlocks::createCompressedBlock); - createGeneratedBlock(material -> material instanceof IngotMaterial && - material.hasFlag(MatFlags.GENERATE_ORE), MetaBlocks::createSurfaceRockBlock); + createGeneratedBlock( + material -> material instanceof DustMaterial && !OrePrefix.block.isIgnored(material), + MetaBlocks::createCompressedBlock); + + createGeneratedBlock( + material -> material instanceof IngotMaterial && material.hasFlag(MatFlags.GENERATE_ORE), + MetaBlocks::createSurfaceRockBlock); for (Material material : Material.MATERIAL_REGISTRY) { if (material instanceof DustMaterial && @@ -203,29 +207,39 @@ public static void init() { Blocks.FIRE.setFireInfo(LEAVES, 30, 60); } - private static int createGeneratedBlock(Predicate materialPredicate, BiConsumer blockGenerator) { - Material[] materialBuffer = new Material[16]; - Arrays.fill(materialBuffer, Materials._NULL); - int currentGenerationIndex = 0; - for (Material material : Material.MATERIAL_REGISTRY) { - if (materialPredicate.test(material)) { - if (currentGenerationIndex > 0 && currentGenerationIndex % 16 == 0) { - blockGenerator.accept(materialBuffer, currentGenerationIndex / 16 - 1); - Arrays.fill(materialBuffer, Materials._NULL); + /** + * Deterministically populates a category of MetaBlocks based on the unique registry ID of each qualifying Material. + * + * @param materialPredicate a filter for determining if a Material qualifies for generation in the category. + * @param blockGenerator a function which accepts a Materials set to pack into a MetaBlock, and the ordinal this + * MetaBlock should have within its category. + */ + protected static void createGeneratedBlock(Predicate materialPredicate, + BiConsumer blockGenerator) { + + Map blocksToGenerate = new TreeMap<>(); + + for(Material material : Material.MATERIAL_REGISTRY) + if(materialPredicate.test(material)) { + int id = Material.MATERIAL_REGISTRY.getIDForObject(material); + int metaBlockID = id / 16; + int subBlockID = id % 16; + + if (!blocksToGenerate.containsKey(metaBlockID)) { + Material[] materials = new Material[16]; + Arrays.fill(materials, Materials._NULL); + blocksToGenerate.put(metaBlockID, materials); } - materialBuffer[currentGenerationIndex % 16] = material; - currentGenerationIndex++; + + blocksToGenerate.get(metaBlockID)[subBlockID] = material; } - } - if (materialBuffer[0] != Materials._NULL) { - blockGenerator.accept(materialBuffer, currentGenerationIndex / 16); - } - return (currentGenerationIndex / 16) + 1; + + blocksToGenerate.forEach((key, value) -> blockGenerator.accept(value, key)); } private static void createSurfaceRockBlock(Material[] materials, int index) { BlockSurfaceRockDeprecated block = new BlockSurfaceRockDeprecated(materials); - block.setRegistryName("surface_rock_" + index); + block.setRegistryName("meta_block_surface_rock_" + index); for (Material material : materials) { if (material instanceof IngotMaterial) { SURFACE_ROCKS.put((IngotMaterial) material, block); @@ -235,7 +249,7 @@ private static void createSurfaceRockBlock(Material[] materials, int index) { private static void createCompressedBlock(Material[] materials, int index) { BlockCompressed block = new BlockCompressed(materials); - block.setRegistryName("compressed_" + index); + block.setRegistryName("meta_block_compressed_" + index); for (Material material : materials) { if (material instanceof DustMaterial) { COMPRESSED.put((DustMaterial) material, block); diff --git a/src/main/java/gregtech/common/blocks/modelfactories/BlockCompressedFactory.java b/src/main/java/gregtech/common/blocks/modelfactories/BlockCompressedFactory.java index 984b38a720..cdc607fbc4 100644 --- a/src/main/java/gregtech/common/blocks/modelfactories/BlockCompressedFactory.java +++ b/src/main/java/gregtech/common/blocks/modelfactories/BlockCompressedFactory.java @@ -32,7 +32,7 @@ public static void init() { } private BlockCompressedFactory() { - super("compressed_block", "compressed_"); + super("compressed_block", "meta_block_compressed_"); } @Override diff --git a/src/main/java/gregtech/common/datafix/GregTechDataFixers.java b/src/main/java/gregtech/common/datafix/GregTechDataFixers.java new file mode 100644 index 0000000000..41ec506ab4 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/GregTechDataFixers.java @@ -0,0 +1,59 @@ +package gregtech.common.datafix; + +import gregtech.api.GTValues; +import gregtech.common.datafix.fixes.Fix0PostGraniteMetaBlockShift; +import gregtech.common.datafix.fixes.Fix0PostGraniteMetaBlockShiftInWorld; +import gregtech.common.datafix.fixes.Fix1MetaBlockIdSystem; +import gregtech.common.datafix.fixes.Fix1MetaBlockIdSystemInWorld; +import gregtech.common.datafix.walker.WalkChunkSection; +import gregtech.common.datafix.walker.WalkItemStackLike; +import net.minecraft.util.datafix.FixTypes; +import net.minecraft.util.datafix.IDataWalker; +import net.minecraftforge.common.util.CompoundDataFixer; +import net.minecraftforge.common.util.ModFixs; +import net.minecraftforge.fml.common.FMLCommonHandler; + +public class GregTechDataFixers { + + /** + * Data version corresponding to before the granite GregTech material was added. + */ + public static final int V_PRE_GRANITE = -1; + + /** + * Data version corresponding to after the granite GregTech material was added. + */ + public static final int V0_POST_GRANITE = 0; + + /** + * Data version corresponding to after the implementation of the new meta block ID system. + */ + public static final int V1_META_BLOCK_ID_REWORK = 1; + + /** + * The current data format version for all of GregTech. Increment this when breaking changes are made and a new + * data fixer needs to be written. All defined version numbers are listed in {@link GregTechDataFixers}. + */ + public static final int DATA_VERSION = V1_META_BLOCK_ID_REWORK; + + /** + * Constants used for fixing NBT data. + */ + public static final String COMPOUND_ID = "id"; + public static final String COMPOUND_META = "Damage"; + + public static void init() { + CompoundDataFixer fmlFixer = FMLCommonHandler.instance().getDataFixer(); + IDataWalker walkItemStackLike = new WalkItemStackLike(); + fmlFixer.registerVanillaWalker(FixTypes.BLOCK_ENTITY, walkItemStackLike); + fmlFixer.registerVanillaWalker(FixTypes.ENTITY, walkItemStackLike); + fmlFixer.registerVanillaWalker(FixTypes.PLAYER, walkItemStackLike); + fmlFixer.registerVanillaWalker(FixTypes.CHUNK, new WalkChunkSection()); + + ModFixs gtFixer = fmlFixer.init(GTValues.MODID, DATA_VERSION); + gtFixer.registerFix(GregTechFixType.ITEM_STACK_LIKE, new Fix0PostGraniteMetaBlockShift()); + gtFixer.registerFix(GregTechFixType.CHUNK_SECTION, new Fix0PostGraniteMetaBlockShiftInWorld()); + gtFixer.registerFix(GregTechFixType.ITEM_STACK_LIKE, new Fix1MetaBlockIdSystem()); + gtFixer.registerFix(GregTechFixType.CHUNK_SECTION, new Fix1MetaBlockIdSystemInWorld()); + } +} diff --git a/src/main/java/gregtech/common/datafix/GregTechFixType.java b/src/main/java/gregtech/common/datafix/GregTechFixType.java new file mode 100644 index 0000000000..7ba6269b1c --- /dev/null +++ b/src/main/java/gregtech/common/datafix/GregTechFixType.java @@ -0,0 +1,22 @@ +package gregtech.common.datafix; + +import net.minecraft.util.datafix.IFixType; + +public enum GregTechFixType implements IFixType { + + /** + * Any compound tag that looks like an item stack. + * That is to say, it has the fields {@code string id}, {@code byte Count}, and {@code short Damage}. + * + * @see gregtech.common.datafix.walker.WalkItemStackLike + */ + ITEM_STACK_LIKE, + + /** + * A vertical section of a chunk containing block state data. + * + * @see gregtech.common.datafix.walker.WalkChunkSection + */ + CHUNK_SECTION + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShift.java b/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShift.java new file mode 100644 index 0000000000..fb559c5249 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShift.java @@ -0,0 +1,36 @@ +package gregtech.common.datafix.fixes; + +import gregtech.common.datafix.GregTechDataFixers; +import gregtech.common.datafix.fixes.metablockid.MetaBlockIdFixHelper; +import gregtech.common.datafix.fixes.metablockid.PreGraniteMetaBlockIdFixer; +import gregtech.common.datafix.fixes.metablockid.WorldDataHooks; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.datafix.IFixableData; + +public class Fix0PostGraniteMetaBlockShift implements IFixableData { + + @Override + public int getFixVersion() { + return 0; + } + + @Override + public NBTTagCompound fixTagCompound(NBTTagCompound compound) { + if (WorldDataHooks.isFixerUnavailable()) { + return compound; + } + + int index = MetaBlockIdFixHelper.getCompressedIndexFromResLoc(compound.getString(GregTechDataFixers.COMPOUND_ID)); + if (index != -1) { + RemappedBlock remapped = ((PreGraniteMetaBlockIdFixer) WorldDataHooks.getMetaBlockIdFixer()) + .remapCompressedPreGraniteToPost(index, compound.getShort(GregTechDataFixers.COMPOUND_META)); + if (remapped != null) { + compound.setString(GregTechDataFixers.COMPOUND_ID, MetaBlockIdFixHelper.COMP_RESLOC_PREF + remapped.id); + compound.setShort(GregTechDataFixers.COMPOUND_META, remapped.data); + } + } + return compound; + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShiftInWorld.java b/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShiftInWorld.java new file mode 100644 index 0000000000..74d946f202 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/Fix0PostGraniteMetaBlockShiftInWorld.java @@ -0,0 +1,38 @@ +package gregtech.common.datafix.fixes; + +import gregtech.common.datafix.fixes.metablockid.PreGraniteMetaBlockIdFixer; +import gregtech.common.datafix.fixes.metablockid.WorldDataHooks; +import gregtech.common.datafix.util.DataFixHelper; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.datafix.IFixableData; + +public class Fix0PostGraniteMetaBlockShiftInWorld implements IFixableData { + + @Override + public int getFixVersion() { + return 0; + } + + @Override + public NBTTagCompound fixTagCompound(NBTTagCompound compound) { + if (WorldDataHooks.isFixerUnavailable()) { + return compound; + } + + PreGraniteMetaBlockIdFixer fixer = (PreGraniteMetaBlockIdFixer) WorldDataHooks.getMetaBlockIdFixer(); + DataFixHelper.rewriteBlocks(compound, (id, data) -> { + int index = fixer.getRemapCacheCompressed().getOldIndex(id); + if (index == -1) { + return null; + } + RemappedBlock remapped = fixer.remapCompressedPreGraniteToPost(index, data); + if (remapped == null) { + return null; + } + return new RemappedBlock(fixer.getRemapCacheCompressed().getOldId(remapped.id), remapped.data); + }); + return compound; + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystem.java b/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystem.java new file mode 100644 index 0000000000..cc3244d303 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystem.java @@ -0,0 +1,75 @@ +package gregtech.common.datafix.fixes; + +import gregtech.common.datafix.GregTechDataFixers; +import gregtech.common.datafix.fixes.metablockid.MetaBlockIdFixHelper; +import gregtech.common.datafix.fixes.metablockid.PostGraniteMetaBlockIdFixer; +import gregtech.common.datafix.fixes.metablockid.WorldDataHooks; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.datafix.IFixableData; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +public class Fix1MetaBlockIdSystem implements IFixableData { + + public Fix1MetaBlockIdSystem() { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public int getFixVersion() { + return 1; + } + + @Override + public NBTTagCompound fixTagCompound(NBTTagCompound compound) { + if (WorldDataHooks.isFixerUnavailable()) { + return compound; + } + + String blockResLoc = compound.getString(GregTechDataFixers.COMPOUND_ID); + int index = MetaBlockIdFixHelper.getCompressedIndexFromResLoc(blockResLoc); + if (index != -1) { + RemappedBlock remapped = ((PostGraniteMetaBlockIdFixer) WorldDataHooks.getMetaBlockIdFixer()) + .remapCompressedPostGraniteToNew(index, compound.getShort(GregTechDataFixers.COMPOUND_META)); + compound.setString(GregTechDataFixers.COMPOUND_ID, MetaBlockIdFixHelper.COMP_RESLOC_PREF_NEW + remapped.id); + compound.setShort(GregTechDataFixers.COMPOUND_META, remapped.data); + return compound; + } + + index = MetaBlockIdFixHelper.getSurfRockIndexFromResLoc(blockResLoc); + if (index != -1) { + RemappedBlock remapped = ((PostGraniteMetaBlockIdFixer) WorldDataHooks.getMetaBlockIdFixer()) + .remapSurfRockToNew(index, compound.getShort(GregTechDataFixers.COMPOUND_META)); + compound.setString(GregTechDataFixers.COMPOUND_ID, MetaBlockIdFixHelper.SURF_ROCK_RESLOC_PREF_NEW + remapped.id); + compound.setShort(GregTechDataFixers.COMPOUND_META, remapped.data); + } + return compound; + } + + @SubscribeEvent + public void onMissingBlockMappings(RegistryEvent.MissingMappings event) { + for (RegistryEvent.MissingMappings.Mapping mapping : event.getMappings()) { + String regName = mapping.key.getPath(); + if (regName.startsWith(MetaBlockIdFixHelper.COMP_NAME_PREF) + || regName.startsWith(MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF)) { + mapping.ignore(); + } + } + } + + @SubscribeEvent + public void onMissingItemMappings(RegistryEvent.MissingMappings event) { + for (RegistryEvent.MissingMappings.Mapping mapping : event.getMappings()) { + String regName = mapping.key.getPath(); + if (regName.startsWith(MetaBlockIdFixHelper.COMP_NAME_PREF) + || regName.startsWith(MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF)) { + mapping.ignore(); + } + } + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystemInWorld.java b/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystemInWorld.java new file mode 100644 index 0000000000..5e3b52d204 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/Fix1MetaBlockIdSystemInWorld.java @@ -0,0 +1,43 @@ +package gregtech.common.datafix.fixes; + +import gregtech.common.datafix.fixes.metablockid.MetaBlockIdRemapCache; +import gregtech.common.datafix.fixes.metablockid.PostGraniteMetaBlockIdFixer; +import gregtech.common.datafix.fixes.metablockid.WorldDataHooks; +import gregtech.common.datafix.util.DataFixHelper; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.datafix.IFixableData; + +public class Fix1MetaBlockIdSystemInWorld implements IFixableData { + + @Override + public int getFixVersion() { + return 1; + } + + @Override + public NBTTagCompound fixTagCompound(NBTTagCompound compound) { + if (WorldDataHooks.isFixerUnavailable()) { + return compound; + } + + PostGraniteMetaBlockIdFixer fixer = (PostGraniteMetaBlockIdFixer) WorldDataHooks.getMetaBlockIdFixer(); + MetaBlockIdRemapCache remapCompressed = fixer.getRemapCacheCompressed(); + MetaBlockIdRemapCache remapSurfRock = fixer.getRemapCacheSurfRock(); + DataFixHelper.rewriteBlocks(compound, (id, data) -> { + int index = remapCompressed.getOldIndex(id); + if (index != -1) { + RemappedBlock remapped = fixer.remapCompressedPostGraniteToNew(index, data); + return new RemappedBlock(remapCompressed.getNewId(remapped.id), remapped.data); + } + index = remapSurfRock.getOldIndex(id); + if (index != -1) { + RemappedBlock remapped = fixer.remapSurfRockToNew(index, data); + return new RemappedBlock(remapSurfRock.getNewId(remapped.id), remapped.data); + } + return null; + }); + return compound; + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixHelper.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixHelper.java new file mode 100644 index 0000000000..c1774ff07e --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixHelper.java @@ -0,0 +1,91 @@ +package gregtech.common.datafix.fixes.metablockid; + +import com.google.common.collect.ImmutableList; +import gregtech.api.GTValues; +import gregtech.api.unification.material.type.Material; +import gregtech.api.util.Version; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.common.util.Constants; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +public class MetaBlockIdFixHelper { + + public static final Version V1_10_5 = new Version(1, 10, 5); // granite was added in 1.10.5 + public static final Version V1_14_0 = new Version(1, 14, 0); // meta block id alloc was changed in 1.14.0 + + public static final String KEY_FALLBACK_VERSION = "FallbackVersion"; + + public static final String COMP_NAME_PREF = "compressed_"; + public static final String COMP_RESLOC_PREF = GTValues.MODID + ":" + COMP_NAME_PREF; + public static final int COMP_RESLOC_PREF_LEN = COMP_RESLOC_PREF.length(); + public static final String COMP_NAME_PREF_NEW = "meta_block_compressed_"; + public static final String COMP_RESLOC_PREF_NEW = GTValues.MODID + ":" + COMP_NAME_PREF_NEW; + + public static int getCompressedIndexFromResLoc(String resLoc) { + if (resLoc.startsWith(COMP_RESLOC_PREF)) { + try { + return Integer.parseInt(resLoc.substring(COMP_RESLOC_PREF_LEN)); + } catch (NumberFormatException ignored) { + } + } + return -1; + } + + public static final String SURF_ROCK_NAME_PREF = "surface_rock_"; + public static final String SURF_ROCK_RESLOC_PREF = GTValues.MODID + ":" + SURF_ROCK_NAME_PREF; + public static final int SURF_ROCK_RESLOC_PREF_LEN = SURF_ROCK_RESLOC_PREF.length(); + public static final String SURF_ROCK_NAME_PREF_NEW = "meta_block_surface_rock_"; + public static final String SURF_ROCK_RESLOC_PREF_NEW = GTValues.MODID + ":" + SURF_ROCK_NAME_PREF_NEW; + + public static int getSurfRockIndexFromResLoc(String resLoc) { + if (resLoc.startsWith(SURF_ROCK_RESLOC_PREF)) { + try { + return Integer.parseInt(resLoc.substring(SURF_ROCK_RESLOC_PREF_LEN)); + } catch (NumberFormatException ignored) { + } + } + return -1; + } + + public static List collectOldMetaBlockAlloc(Predicate filter) { + ImmutableList.Builder builder = ImmutableList.builder(); + int[] buffer = new int[16]; + Arrays.fill(buffer, -1); + int bufferPtr = 0; + for (Material material : Material.MATERIAL_REGISTRY) { + if (filter.test(material)) { + buffer[bufferPtr++] = Material.MATERIAL_REGISTRY.getIDForObject(material); + if (bufferPtr >= 16) { + builder.add(buffer); + buffer = new int[16]; + Arrays.fill(buffer, -1); + bufferPtr = 0; + } + } + } + if (bufferPtr > 0) { + builder.add(buffer); + } + return builder.build(); + } + + @Nullable + public static NBTTagList getBlockRegistryTag(NBTTagCompound fmlTag) { + if (fmlTag.hasKey("Registries", Constants.NBT.TAG_COMPOUND)) { + NBTTagCompound tag = fmlTag.getCompoundTag("Registries"); + if (tag.hasKey("minecraft:blocks", Constants.NBT.TAG_COMPOUND)) { + tag = tag.getCompoundTag("minecraft:blocks"); + if (tag.hasKey("ids", Constants.NBT.TAG_LIST)) { + return tag.getTagList("ids", Constants.NBT.TAG_COMPOUND); + } + } + } + return null; + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixer.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixer.java new file mode 100644 index 0000000000..6120116202 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdFixer.java @@ -0,0 +1,58 @@ +package gregtech.common.datafix.fixes.metablockid; + +import gregtech.api.util.Version; +import gregtech.common.datafix.GregTechDataFixers; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.Constants; + +public interface MetaBlockIdFixer { + + MetaBlockIdFixer NOOP = new Noop(); + + static MetaBlockIdFixer create(Version prevSaveVersion, NBTTagCompound fmlTag) { + if (prevSaveVersion.compareTo(MetaBlockIdFixHelper.V1_10_5) < 0) { + return PreGraniteMetaBlockIdFixer.generate(fmlTag); + } else if (prevSaveVersion.compareTo(MetaBlockIdFixHelper.V1_14_0) < 0) { + return PostGraniteMetaBlockIdFixer.generate(fmlTag); + } else { + return NOOP; + } + } + + int getFallbackDataVersion(); + + NBTTagCompound serialize(); + + static MetaBlockIdFixer deserialize(NBTTagCompound tag) { + if (tag.hasKey(MetaBlockIdFixHelper.KEY_FALLBACK_VERSION, Constants.NBT.TAG_INT)) { + int fallbackVersion = tag.getInteger(MetaBlockIdFixHelper.KEY_FALLBACK_VERSION); + switch (fallbackVersion) { + case GregTechDataFixers.V_PRE_GRANITE: + return PreGraniteMetaBlockIdFixer.deserialize(tag); + case GregTechDataFixers.V0_POST_GRANITE: + return PostGraniteMetaBlockIdFixer.deserialize(tag); + default: + throw new IllegalStateException("Bad GregTech fallback data version: " + fallbackVersion); + } + } + return NOOP; + } + + class Noop implements MetaBlockIdFixer { + + private Noop() { + } + + @Override + public int getFallbackDataVersion() { + return GregTechDataFixers.V1_META_BLOCK_ID_REWORK; + } + + @Override + public NBTTagCompound serialize() { + return new NBTTagCompound(); + } + + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdRemapCache.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdRemapCache.java new file mode 100644 index 0000000000..bcf051db7a --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/MetaBlockIdRemapCache.java @@ -0,0 +1,158 @@ +package gregtech.common.datafix.fixes.metablockid; + +import gnu.trove.iterator.TIntIntIterator; +import gnu.trove.map.TIntIntMap; +import gnu.trove.map.hash.TIntIntHashMap; +import gregtech.api.GTValues; +import net.minecraft.block.Block; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.ToIntFunction; + +public class MetaBlockIdRemapCache { + + private static final String KEY_ID_MAPPING = "IdMapping"; + private static final int SER_MASK_ID = 0xFFFFFFC0, SER_MASK_INDEX = 0x0000003F; + + private final String newNamePrefix; + private final TIntIntMap idToIndex, indexToId; + // gregtech registry IDs are capped at 999, so we can't have more than 63 * 16 = 1008 entries + private final int[] newIdCache = new int[63]; + + private MetaBlockIdRemapCache(String newNamePrefix, TIntIntMap idToIndex, TIntIntMap indexToId) { + this.newNamePrefix = newNamePrefix; + this.idToIndex = idToIndex; + this.indexToId = indexToId; + Arrays.fill(newIdCache, -1); + } + + public int getOldIndex(int id) { + return idToIndex.get(id); + } + + public int getOldId(int index) { + return indexToId.get(index); + } + + public int getNewId(int index) { + int id = newIdCache[index]; + if (id != -1) { + return id; + } + newIdCache[index] = Block.getIdFromBlock(Objects.requireNonNull(ForgeRegistries.BLOCKS.getValue( + new ResourceLocation(GTValues.MODID, newNamePrefix + index)))); + + return newIdCache[index]; + } + + public NBTTagCompound serialize() { + NBTTagCompound tag = new NBTTagCompound(); + + // only need to serialize the old id/index mappings, since newNamePrefix is a constant + int[] idMapSer = new int[idToIndex.size()]; + int entryNdx = 0; + TIntIntIterator iter = idToIndex.iterator(); + while (iter.hasNext()) { + iter.advance(); + // 26-bit block ID, 6-bit meta block index + idMapSer[entryNdx++] = ((iter.key() << 6) & SER_MASK_ID) | (iter.value() & SER_MASK_INDEX); + } + tag.setIntArray(KEY_ID_MAPPING, idMapSer); + + return tag; + } + + public static MetaBlockIdRemapCache deserialize(String newNamePrefix, NBTTagCompound tag) { + int[] idMapSer = tag.getIntArray(KEY_ID_MAPPING); + TIntIntMap idToIndex = new TIntIntHashMap(idMapSer.length, 1.1F, -1, -1); + TIntIntMap indexToId = new TIntIntHashMap(idMapSer.length, 1.1F, -1, -1); + for (int entrySer : idMapSer) { + // 26-bit block ID, 6-bit meta block index + int id = (entrySer & SER_MASK_ID) >>> 6; + int index = entrySer & SER_MASK_INDEX; + idToIndex.put(id, index); + indexToId.put(index, id); + } + return new MetaBlockIdRemapCache(newNamePrefix, idToIndex, indexToId); + } + + public static MetaBlockIdRemapCache[] generate(NBTTagList regEntryListTag, Spec... metaBlockSpecs) { + // array for collecting id/index mappings + MappingCollection[] mappings = new MappingCollection[metaBlockSpecs.length]; + for (int specNdx = 0; specNdx < metaBlockSpecs.length; specNdx++) { + mappings[specNdx] = new MappingCollection(); + } + + // iterate registry and populate mappings + int maxBlockId = -1; + for (int i = 0; i < regEntryListTag.tagCount(); i++) { + NBTTagCompound regEntryTag = regEntryListTag.getCompoundTagAt(i); + int id = regEntryTag.getInteger("V"); + if (id > maxBlockId) { + maxBlockId = id; + } + for (int specNdx = 0; specNdx < metaBlockSpecs.length; specNdx++) { + int index = metaBlockSpecs[specNdx].indexParser.applyAsInt(regEntryTag.getString("K")); + if (index != -1) { + MappingCollection mapping = mappings[specNdx]; + mapping.idToIndex.put(id, index); + mapping.indexToId.put(index, id); + if (index > mapping.maxIndex) { + mapping.maxIndex = index; + } + break; // any one block can only be part of one meta block + } + } + } + + // add dummy entry for the max meta block index + 1, in case the addition of an ID overflows existing indices + // necessary for mapping compressed blocks from pre-granite to post, for example + for (int specNdx = 0; specNdx < metaBlockSpecs.length; specNdx++) { + if (metaBlockSpecs[specNdx].addAdditionalIndex) { + ++maxBlockId; + MappingCollection mapping = mappings[specNdx]; + mapping.idToIndex.put(maxBlockId, mapping.maxIndex + 1); + mapping.indexToId.put(mapping.maxIndex + 1, maxBlockId); + } + } + + // bake mappings into MetaBlockIdRemapCache instances + MetaBlockIdRemapCache[] result = new MetaBlockIdRemapCache[mappings.length]; + for (int specNdx = 0; specNdx < metaBlockSpecs.length; specNdx++) { + result[specNdx] = mappings[specNdx].bake(metaBlockSpecs[specNdx].newNamePrefix); + } + return result; + } + + public static class Spec { + + final String newNamePrefix; + final ToIntFunction indexParser; + final boolean addAdditionalIndex; + + public Spec(String newNamePrefix, ToIntFunction indexParser, boolean addAdditionalIndex) { + this.newNamePrefix = newNamePrefix; + this.indexParser = indexParser; + this.addAdditionalIndex = addAdditionalIndex; + } + + } + + private static class MappingCollection { + + final TIntIntMap idToIndex = new TIntIntHashMap(16, 0.8F, -1, -1); + final TIntIntMap indexToId = new TIntIntHashMap(16, 0.8F, -1, -1); + int maxIndex = -1; + + MetaBlockIdRemapCache bake(String newNamePrefix) { + return new MetaBlockIdRemapCache(newNamePrefix, idToIndex, indexToId); + } + + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/PostGraniteMetaBlockIdFixer.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/PostGraniteMetaBlockIdFixer.java new file mode 100644 index 0000000000..4b451d350f --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/PostGraniteMetaBlockIdFixer.java @@ -0,0 +1,102 @@ +package gregtech.common.datafix.fixes.metablockid; + +import gregtech.api.unification.material.type.DustMaterial; +import gregtech.api.unification.material.type.IngotMaterial; +import gregtech.api.unification.ore.OrePrefix; +import gregtech.common.datafix.GregTechDataFixers; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import javax.annotation.Nullable; +import java.util.List; + +public class PostGraniteMetaBlockIdFixer implements MetaBlockIdFixer { + + protected static final String KEY_REMAP_CACHE_COMPRESSED = "RemapCacheCompressed"; + protected static final String KEY_REMAP_CACHE_SURF_ROCK = "RemapCacheSurfaceRock"; + + @Nullable + private static List tableOldAllocCompressed = null; + @Nullable + private static List tableOldAllocSurfRock = null; + + protected static List getTableOldAllocCompressed() { + if (tableOldAllocCompressed == null) { + tableOldAllocCompressed = MetaBlockIdFixHelper.collectOldMetaBlockAlloc( + mat -> mat instanceof DustMaterial && !OrePrefix.block.isIgnored(mat)); + } + return tableOldAllocCompressed; + } + + protected static List getTableOldAllocSurfRock() { + if (tableOldAllocSurfRock == null) { + tableOldAllocSurfRock = MetaBlockIdFixHelper.collectOldMetaBlockAlloc( + mat -> mat instanceof IngotMaterial && mat.hasFlag(DustMaterial.MatFlags.GENERATE_ORE)); + } + return tableOldAllocSurfRock; + } + + private final MetaBlockIdRemapCache remapCacheCompressed; + private final MetaBlockIdRemapCache remapCacheSurfRock; + + public PostGraniteMetaBlockIdFixer(MetaBlockIdRemapCache remapCacheCompressed, + MetaBlockIdRemapCache remapCacheSurfRock) { + this.remapCacheCompressed = remapCacheCompressed; + this.remapCacheSurfRock = remapCacheSurfRock; + } + + public static PostGraniteMetaBlockIdFixer generate(NBTTagCompound fmlTag) { + NBTTagList blockRegistryTag = MetaBlockIdFixHelper.getBlockRegistryTag(fmlTag); + if (blockRegistryTag == null) { + throw new IllegalStateException("Block registry is not serialized in level data!"); + } + MetaBlockIdRemapCache[] remapCaches = MetaBlockIdRemapCache.generate(blockRegistryTag, + new MetaBlockIdRemapCache.Spec( + MetaBlockIdFixHelper.COMP_NAME_PREF_NEW, MetaBlockIdFixHelper::getCompressedIndexFromResLoc, false), + new MetaBlockIdRemapCache.Spec( + MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF_NEW, MetaBlockIdFixHelper::getSurfRockIndexFromResLoc, false)); + return new PostGraniteMetaBlockIdFixer(remapCaches[0], remapCaches[1]); + } + + @Override + public int getFallbackDataVersion() { + return GregTechDataFixers.V0_POST_GRANITE; + } + + public MetaBlockIdRemapCache getRemapCacheCompressed() { + return remapCacheCompressed; + } + + public MetaBlockIdRemapCache getRemapCacheSurfRock() { + return remapCacheSurfRock; + } + + public RemappedBlock remapCompressedPostGraniteToNew(int index, int data) { + int matId = getTableOldAllocCompressed().get(index)[data]; + return new RemappedBlock(matId / 16, (short) (matId % 16)); + } + + public RemappedBlock remapSurfRockToNew(int index, int data) { + int matId = getTableOldAllocSurfRock().get(index)[data]; + return new RemappedBlock(matId / 16, (short) (matId % 16)); + } + + @Override + public NBTTagCompound serialize() { + NBTTagCompound tag = new NBTTagCompound(); + tag.setInteger(MetaBlockIdFixHelper.KEY_FALLBACK_VERSION, 0); + tag.setTag(KEY_REMAP_CACHE_COMPRESSED, remapCacheCompressed.serialize()); + tag.setTag(KEY_REMAP_CACHE_SURF_ROCK, remapCacheSurfRock.serialize()); + return tag; + } + + public static PostGraniteMetaBlockIdFixer deserialize(NBTTagCompound tag) { + return new PostGraniteMetaBlockIdFixer( + MetaBlockIdRemapCache.deserialize( + MetaBlockIdFixHelper.COMP_NAME_PREF_NEW, tag.getCompoundTag(KEY_REMAP_CACHE_COMPRESSED)), + MetaBlockIdRemapCache.deserialize( + MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF_NEW, tag.getCompoundTag(KEY_REMAP_CACHE_SURF_ROCK))); + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/PreGraniteMetaBlockIdFixer.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/PreGraniteMetaBlockIdFixer.java new file mode 100644 index 0000000000..3ca7d2e473 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/PreGraniteMetaBlockIdFixer.java @@ -0,0 +1,67 @@ +package gregtech.common.datafix.fixes.metablockid; + +import gregtech.api.unification.material.Materials; +import gregtech.api.unification.material.type.Material; +import gregtech.common.datafix.GregTechDataFixers; +import gregtech.common.datafix.util.RemappedBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import javax.annotation.Nullable; + +public class PreGraniteMetaBlockIdFixer extends PostGraniteMetaBlockIdFixer { + + private static final int GRANITE_ID = Material.MATERIAL_REGISTRY.getIDForObject(Materials.Granite); + + public PreGraniteMetaBlockIdFixer(MetaBlockIdRemapCache remapCacheCompressed, + MetaBlockIdRemapCache remapCacheSurfRock) { + super(remapCacheCompressed, remapCacheSurfRock); + } + + public static PreGraniteMetaBlockIdFixer generate(NBTTagCompound fmlTag) { + NBTTagList blockRegistryTag = MetaBlockIdFixHelper.getBlockRegistryTag(fmlTag); + if (blockRegistryTag == null) { + throw new IllegalStateException("Block registry is not serialized in level data!"); + } + MetaBlockIdRemapCache[] remapCaches = MetaBlockIdRemapCache.generate(blockRegistryTag, + new MetaBlockIdRemapCache.Spec( + MetaBlockIdFixHelper.COMP_NAME_PREF_NEW, MetaBlockIdFixHelper::getCompressedIndexFromResLoc, true), + new MetaBlockIdRemapCache.Spec( + MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF_NEW, MetaBlockIdFixHelper::getSurfRockIndexFromResLoc, false)); + return new PreGraniteMetaBlockIdFixer(remapCaches[0], remapCaches[1]); + } + + @Override + public int getFallbackDataVersion() { + return GregTechDataFixers.V_PRE_GRANITE; + } + + @Nullable + public RemappedBlock remapCompressedPreGraniteToPost(int index, int data) { + int matId = getTableOldAllocCompressed().get(index)[data]; + if (matId >= GRANITE_ID) { + if (data == 15) { + return new RemappedBlock(index + 1, (short) 0); + } else { + return new RemappedBlock(index, (short) (data + 1)); + } + } + return null; + } + + @Override + public NBTTagCompound serialize() { + NBTTagCompound tag = super.serialize(); + tag.setInteger(MetaBlockIdFixHelper.KEY_FALLBACK_VERSION, -1); + return tag; + } + + public static PreGraniteMetaBlockIdFixer deserialize(NBTTagCompound tag) { + return new PreGraniteMetaBlockIdFixer( + MetaBlockIdRemapCache.deserialize( + MetaBlockIdFixHelper.COMP_NAME_PREF_NEW, tag.getCompoundTag(KEY_REMAP_CACHE_COMPRESSED)), + MetaBlockIdRemapCache.deserialize( + MetaBlockIdFixHelper.SURF_ROCK_NAME_PREF_NEW, tag.getCompoundTag(KEY_REMAP_CACHE_SURF_ROCK))); + } + +} diff --git a/src/main/java/gregtech/common/datafix/fixes/metablockid/WorldDataHooks.java b/src/main/java/gregtech/common/datafix/fixes/metablockid/WorldDataHooks.java new file mode 100644 index 0000000000..9e371cd8cf --- /dev/null +++ b/src/main/java/gregtech/common/datafix/fixes/metablockid/WorldDataHooks.java @@ -0,0 +1,150 @@ +package gregtech.common.datafix.fixes.metablockid; + +import gregtech.GregTechVersion; +import gregtech.api.GTValues; +import gregtech.api.util.GTLog; +import gregtech.api.util.Version; +import gregtech.common.datafix.GregTechDataFixers; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.storage.SaveHandler; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.StartupQuery; +import net.minecraftforge.fml.common.ZipperUtil; +import net.minecraftforge.fml.relauncher.Side; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Objects; + +public class WorldDataHooks { + + private static final String MAP_STORAGE_NAME = "gregtech_data"; + private static final String KEY_META_BLOCK_ID_FIXER = "MetaBlockIdFixer"; + + @Nullable + private static MetaBlockIdFixer metaBlockIdFixer = null; + + private WorldDataHooks() { + } + + public static boolean isFixerUnavailable() { + return metaBlockIdFixer == null; + } + + public static MetaBlockIdFixer getMetaBlockIdFixer() { + return Objects.requireNonNull(metaBlockIdFixer); + } + + @SuppressWarnings("unused") + public static void onWorldLoad(SaveHandler saveHandler, NBTTagCompound levelTag) { + // the meta block ID fix only matters on the server side + if (FMLCommonHandler.instance().getEffectiveSide() != Side.SERVER) { + return; + } + + metaBlockIdFixer = null; // we want to get our hands on one of these + boolean firstTimeFixing = false; // is it the first time running an old world with fixers? + + File mapStorageFile = saveHandler.getMapFileFromName(MAP_STORAGE_NAME); + NBTTagCompound wsdDataTag = null; + boolean wsdNeedsWrite = true; // do we need to serialize the fixer instance to the WSD? + + // load fixer instance from gt world-saved data + if (mapStorageFile.exists()) { + try (FileInputStream mapStorageIn = new FileInputStream(mapStorageFile)) { + wsdDataTag = CompressedStreamTools.readCompressed(mapStorageIn).getCompoundTag("data"); + if (wsdDataTag.hasKey(KEY_META_BLOCK_ID_FIXER, Constants.NBT.TAG_COMPOUND)) { + metaBlockIdFixer = MetaBlockIdFixer.deserialize(wsdDataTag.getCompoundTag(KEY_META_BLOCK_ID_FIXER)); + GTLog.logger.info("Using fallback data version {} from WSD", metaBlockIdFixer.getFallbackDataVersion()); + wsdNeedsWrite = false; // we just loaded it, so there's no need to write it back + } + } catch (IOException e) { + throw new IllegalStateException("Failed to read GregTech world-saved data!", e); + } + } + + // that failing, create a new one by reading the level NBT + if (metaBlockIdFixer == null && levelTag.hasKey("FML", Constants.NBT.TAG_COMPOUND)) { + NBTTagCompound fmlTag = levelTag.getCompoundTag("FML"); + if (fmlTag.hasKey("ModList", Constants.NBT.TAG_LIST)) { + NBTTagList modListTag = fmlTag.getTagList("ModList", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < modListTag.tagCount(); i++) { + NBTTagCompound modEntryTag = modListTag.getCompoundTagAt(i); + if (modEntryTag.getString("ModId").equals(GTValues.MODID)) { + Version prevSaveVersion = Version.parse(modEntryTag.getString("ModVersion")); + metaBlockIdFixer = MetaBlockIdFixer.create(prevSaveVersion, fmlTag); + GTLog.logger.info("Using fallback data version {} from previous GregTech version {}", + metaBlockIdFixer.getFallbackDataVersion(), prevSaveVersion); + if (metaBlockIdFixer.getFallbackDataVersion() < GregTechDataFixers.V1_META_BLOCK_ID_REWORK) { + firstTimeFixing = true; + } + } + } + } + } + + // that failing, assume world has never been played with gt before, so we're already up-to-date + if (metaBlockIdFixer == null) { + metaBlockIdFixer = MetaBlockIdFixer.NOOP; + GTLog.logger.info("Using fallback data version 1 by default"); + } + + // if needed, serialize the fixer instance to world-saved data + if (wsdNeedsWrite) { + if (wsdDataTag == null) { + wsdDataTag = new NBTTagCompound(); + } + + wsdDataTag.setTag(KEY_META_BLOCK_ID_FIXER, metaBlockIdFixer.serialize()); + NBTTagCompound wsdTag = new NBTTagCompound(); + wsdTag.setTag("data", wsdDataTag); + try (FileOutputStream mapStorageOut = new FileOutputStream(mapStorageFile)) { + CompressedStreamTools.writeCompressed(wsdTag, mapStorageOut); + } catch (IOException e) { + throw new IllegalStateException("Failed to write GregTech world-saved data!", e); + } + } + + // prompt to create a backup if it's the first time an old world is being loaded with fixers + if (firstTimeFixing) { + promptWorldBackup(metaBlockIdFixer.getFallbackDataVersion()); + } + } + + public static void promptWorldBackup(int prevDataVersion) { + String text = "GregTech detected a required registry remapping!\n\n" + + "Updating from before " + TextFormatting.AQUA + (prevDataVersion == -1 ? MetaBlockIdFixHelper.V1_10_5 : MetaBlockIdFixHelper.V1_14_0) + TextFormatting.RESET + + " to " + TextFormatting.AQUA + GregTechVersion.VERSION.toString(3) + TextFormatting.RESET + ".\n" + + "It is " + TextFormatting.UNDERLINE + "strongly" + TextFormatting.RESET + " recommended that you perform a backup. Create backup?"; + + if (StartupQuery.confirm(text)) { + try { + GTLog.logger.info("Creating world backup before starting registry remapping..."); + ZipperUtil.backupWorld(); + } catch (IOException e) { + GTLog.logger.error(e); + StartupQuery.notify("Encountered an error while creating the backup!\n" + + "See the game log for more details."); + StartupQuery.abort(); + } + } else { + String reconfirm = "No backup will be created. Proceed with the remapping without a backup?"; + if (!StartupQuery.confirm(reconfirm)) + StartupQuery.abort(); + } + } + + @SuppressWarnings("unused") + public static int getFallbackModVersion(String modId) { + return metaBlockIdFixer != null && modId.equals(GTValues.MODID) + ? metaBlockIdFixer.getFallbackDataVersion() : -1; + } + +} diff --git a/src/main/java/gregtech/common/datafix/util/DataFixHelper.java b/src/main/java/gregtech/common/datafix/util/DataFixHelper.java new file mode 100644 index 0000000000..102c80bd5a --- /dev/null +++ b/src/main/java/gregtech/common/datafix/util/DataFixHelper.java @@ -0,0 +1,94 @@ +package gregtech.common.datafix.util; + +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.world.chunk.NibbleArray; +import net.minecraftforge.common.util.Constants; + +import javax.annotation.Nullable; + +public class DataFixHelper { + + public static void rewriteCompoundTags(NBTTagCompound tag, CompoundRewriter rewriter) { + for (String key : tag.getKeySet()) { + NBTBase childTag = tag.getTag(key); + switch (childTag.getId()) { + case Constants.NBT.TAG_LIST: + rewriteCompoundTags((NBTTagList) childTag, rewriter); + break; + case Constants.NBT.TAG_COMPOUND: + NBTTagCompound childTagCompound = (NBTTagCompound) childTag; + rewriteCompoundTags(childTagCompound, rewriter); + childTagCompound = rewriter.rewrite(childTagCompound); + if (childTagCompound != null) { + tag.setTag(key, childTagCompound); + } + break; + } + } + } + + public static void rewriteCompoundTags(NBTTagList tag, CompoundRewriter rewriter) { + for (int i = 0; i < tag.tagCount(); i++) { + NBTBase childTag = tag.get(i); + switch (childTag.getId()) { + case Constants.NBT.TAG_LIST: + rewriteCompoundTags((NBTTagList) childTag, rewriter); + break; + case Constants.NBT.TAG_COMPOUND: + NBTTagCompound childTagCompound = (NBTTagCompound) childTag; + rewriteCompoundTags(childTagCompound, rewriter); + childTagCompound = rewriter.rewrite(childTagCompound); + if (childTagCompound != null) { + tag.set(i, childTagCompound); + } + break; + } + } + } + + @FunctionalInterface + public interface CompoundRewriter { + + @Nullable + NBTTagCompound rewrite(NBTTagCompound tag); + + } + + public static void rewriteBlocks(NBTTagCompound chunkSectionTag, BlockRewriter rewriter) { + byte[] blockIds = chunkSectionTag.getByteArray("Blocks"); + NibbleArray blockData = new NibbleArray(chunkSectionTag.getByteArray("Data")); + NibbleArray extendedIds = chunkSectionTag.hasKey("Add", Constants.NBT.TAG_BYTE_ARRAY) + ? new NibbleArray(chunkSectionTag.getByteArray("Add")) : null; + for (int i = 0; i < 4096; ++i) { + int x = i & 0x0F, y = i >> 8 & 0x0F, z = i >> 4 & 0x0F; + int id = extendedIds == null ? (blockIds[i] & 0xFF) + : ((blockIds[i] & 0xFF) | (extendedIds.get(x, y, z) << 8)); + RemappedBlock remapped = rewriter.rewrite(id, (short) blockData.get(x, y, z)); + if (remapped != null) { + blockIds[i] = (byte) (remapped.id & 0xFF); + int idExt = (remapped.id >> 8) & 0x0F; + if (idExt != 0) { + if (extendedIds == null) { + extendedIds = new NibbleArray(); + } + extendedIds.set(x, y, z, idExt); + } + blockData.set(x, y, z, remapped.data & 0x0F); + } + } + if (extendedIds != null) { + chunkSectionTag.setByteArray("Add", extendedIds.getData()); + } + } + + @FunctionalInterface + public interface BlockRewriter { + + @Nullable + RemappedBlock rewrite(int id, short data); + + } + +} diff --git a/src/main/java/gregtech/common/datafix/util/RemappedBlock.java b/src/main/java/gregtech/common/datafix/util/RemappedBlock.java new file mode 100644 index 0000000000..2e798ede6d --- /dev/null +++ b/src/main/java/gregtech/common/datafix/util/RemappedBlock.java @@ -0,0 +1,13 @@ +package gregtech.common.datafix.util; + +public class RemappedBlock { + + public final int id; + public final short data; + + public RemappedBlock(int id, short data) { + this.id = id; + this.data = data; + } + +} diff --git a/src/main/java/gregtech/common/datafix/walker/WalkChunkSection.java b/src/main/java/gregtech/common/datafix/walker/WalkChunkSection.java new file mode 100644 index 0000000000..f6c50bcd52 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/walker/WalkChunkSection.java @@ -0,0 +1,27 @@ +package gregtech.common.datafix.walker; + +import gregtech.common.datafix.GregTechFixType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.datafix.IDataFixer; +import net.minecraft.util.datafix.IDataWalker; +import net.minecraftforge.common.util.Constants; + +public class WalkChunkSection implements IDataWalker { + + @Override + public NBTTagCompound process(IDataFixer fixer, NBTTagCompound compound, int versionIn) { + if (compound.hasKey("Level", Constants.NBT.TAG_COMPOUND)) { + NBTTagCompound levelTag = compound.getCompoundTag("Level"); + if (levelTag.hasKey("Sections", Constants.NBT.TAG_LIST)) { + NBTTagList sectionListTag = levelTag.getTagList("Sections", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < sectionListTag.tagCount(); i++) { + sectionListTag.set(i, fixer.process( + GregTechFixType.CHUNK_SECTION, sectionListTag.getCompoundTagAt(i), versionIn)); + } + } + } + return compound; + } + +} diff --git a/src/main/java/gregtech/common/datafix/walker/WalkItemStackLike.java b/src/main/java/gregtech/common/datafix/walker/WalkItemStackLike.java new file mode 100644 index 0000000000..857819d250 --- /dev/null +++ b/src/main/java/gregtech/common/datafix/walker/WalkItemStackLike.java @@ -0,0 +1,24 @@ +package gregtech.common.datafix.walker; + +import gregtech.common.datafix.util.DataFixHelper; +import gregtech.common.datafix.GregTechFixType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.datafix.IDataFixer; +import net.minecraft.util.datafix.IDataWalker; +import net.minecraftforge.common.util.Constants; + +public class WalkItemStackLike implements IDataWalker { + + @Override + public NBTTagCompound process(IDataFixer fixer, NBTTagCompound compound, int versionIn) { + DataFixHelper.rewriteCompoundTags(compound, tag -> { + if (tag.hasKey("id", Constants.NBT.TAG_STRING) && tag.hasKey("Count", Constants.NBT.TAG_BYTE) + && tag.hasKey("Damage", Constants.NBT.TAG_SHORT)) { + return fixer.process(GregTechFixType.ITEM_STACK_LIKE, tag, versionIn); + } + return null; + }); + return compound; + } + +} diff --git a/src/test/java/gregtech/common/blocks/Mapping.java b/src/test/java/gregtech/common/blocks/Mapping.java new file mode 100644 index 0000000000..4a5ff97f89 --- /dev/null +++ b/src/test/java/gregtech/common/blocks/Mapping.java @@ -0,0 +1,45 @@ +package gregtech.common.blocks; + +import gregtech.api.unification.material.type.*; + +/** + * Helper class for tracking MetaBlock sub-block mapping data and printing them nicely. + */ +public class Mapping implements Comparable { + /** Index of the MetaBlock within its category */ + public final int metaBlockNumber; + /** Index of a material within the MetaBlock */ + public final int materialIndex; + /** The Material this mapping pertains to */ + public final Material material; + + public Mapping(int metaBlockNumber, int materialIndex, Material material) { + this.metaBlockNumber = metaBlockNumber; + this.materialIndex = materialIndex; + this.material = material; + } + + public String toString() { + return String.format("_%d:%d \"%s\"", metaBlockNumber, materialIndex, material); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Mapping)) + return false; + Mapping o = (Mapping) other; + return (other == this || + (o.metaBlockNumber == this.metaBlockNumber && + o.materialIndex == this.materialIndex && + o.material.compareTo(this.material) == 0)); + } + + @Override public int compareTo(Mapping other) { + int order = Integer.compare(this.metaBlockNumber, other.metaBlockNumber); + if(order == 0) + order = Integer.compare(this.materialIndex, other.materialIndex); + if(order == 0) + order = this.material.compareTo(other.material); + return order; + } +} \ No newline at end of file diff --git a/src/test/java/gregtech/common/blocks/MetaBlocksTest.java b/src/test/java/gregtech/common/blocks/MetaBlocksTest.java new file mode 100644 index 0000000000..4e1960f9e3 --- /dev/null +++ b/src/test/java/gregtech/common/blocks/MetaBlocksTest.java @@ -0,0 +1,163 @@ +package gregtech.common.blocks; + +import gregtech.api.unification.material.*; +import gregtech.api.unification.material.type.*; +import gregtech.api.unification.ore.*; +import net.minecraft.init.*; +import org.junit.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.Assert.*; + +@Ignore // remove this annotation to run the "tests" which just dump mapping info to STDOUT. +public class MetaBlocksTest { + + /** + * Required. Without this all item-related operations will fail because registries haven't been initialized. + */ + @BeforeClass + public static void bootStrap() { + Bootstrap.register(); + Materials.register(); + } + + /** Use this to print BlockCompressed mappings to console. */ + private static final Consumer blockPrint = m -> System.out.println("gregtech:compressed" + m); + + /** Use this to print Surface Rock mappings to console. */ + private static final Consumer rockPrint = m -> System.out.println("gregtech:surface_rock" + m); + + /** + * Creates a BiConsumer that fills a provided Set with Mappings, suitable for use with + * MetaBlocks::createGeneratedBlock and its Deprecated alternative. + * + * @param filter a predicate to ensure that the Mappings conform to the expected type (pretty much to exclude + * {@link Materials#_NULL} from the results). + * @param resultSet the Mapping set to populate in the returned BiConsumer + * @return the resulting BiConsumer + */ + private static BiConsumer generateMappingSet(Predicate filter, + Set resultSet) { + return (materials, blockNumber) -> { + for(int metaNumber = 0; metaNumber < materials.length; metaNumber++) { + final Material material = materials[metaNumber]; + if (filter.test(material)) + resultSet.add(new Mapping(blockNumber, metaNumber, material)); + } + }; + } + + @Test + public void simulateOldSurfaceRocksMapping() { + Set results = new TreeSet<>(); + createGeneratedBlockDeprecated( + material -> material instanceof IngotMaterial && material.hasFlag(DustMaterial.MatFlags.GENERATE_ORE), + generateMappingSet(m -> m instanceof IngotMaterial, results)); + + System.out.println("--- Old Surface Rock Mappings ---"); + results.forEach(rockPrint); + } + + @Test + public void simulateNewSurfaceRocksMapping() { + Set results = new TreeSet<>(); + MetaBlocks.createGeneratedBlock( + material -> material instanceof IngotMaterial && material.hasFlag(DustMaterial.MatFlags.GENERATE_ORE), + generateMappingSet(m -> m instanceof IngotMaterial, results)); + + System.out.println("--- New Surface Rock Mappings ---"); + results.forEach(rockPrint); + } + + @Test + public void simulateOldCompressedBlockMapping() { + Set toIgnore = new HashSet<>(); + toIgnore.add(Materials.Granite); + + Set mappings = simulateOldCompressedBlockMapping(toIgnore); + Set mappings2 = simulateOldCompressedBlockMapping(Collections.emptySet()); + + printFullMappings(mappings, mappings2); + printDiffMappings(mappings, mappings2); + } + + private static void printFullMappings(Set mappings, Set mappings2) { + System.out.println("--- OLD MAPPINGS BEFORE GRANITE ---"); + mappings.forEach(blockPrint); + System.out.println("\n\n--- OLD MAPPINGS AFTER GRANITE ---"); + mappings2.forEach(blockPrint); + } + + private static void printDiffMappings(Set mappings, Set mappings2) { + Set diff = new TreeSet<>(mappings2); + assertTrue(diff.removeAll(mappings)); + System.out.println("--- OLD MAPPINGS DIFF: AFTER GRANITE - BEFORE GRANITE ---"); + diff.forEach(blockPrint); + + System.out.println("\n\n----------\n\n"); + + Set diff2 = new TreeSet<>(mappings); + diff2.removeAll(mappings2); + System.out.println("--- OLD MAPPINGS DIFF: BEFORE GRANITE - AFTER GRANITE ---"); + diff2.forEach(blockPrint); + } + + private static Set simulateOldCompressedBlockMapping(final Set toIgnore) { + final Set oldMappings = new TreeSet<>(); + createGeneratedBlockDeprecated( + material -> !toIgnore.contains(material) && + material instanceof DustMaterial && + !OrePrefix.block.isIgnored(material), + generateMappingSet(m -> m instanceof DustMaterial, oldMappings) + ); + + return oldMappings; + } + + @Test + public void simulateNewBlockMappings() { + Set results = new TreeSet<>(); + MetaBlocks.createGeneratedBlock( + material -> material instanceof DustMaterial && !OrePrefix.block.isIgnored(material), + generateMappingSet(m -> m instanceof DustMaterial, results) + ); + + System.out.println("--- New Compressed Block Mappings ---"); + results.forEach(blockPrint); + } + + /** + * Old generation code for initializing BlockCompressed and BlockSurfaceRockDeprecated. + * + * Needed for accurately reproducing the deprecated MetaBlock packing behavior for determining what the old blocks + * are so we can remap them to the correct replacements. + * + * @param materialPredicate a filter for determining if a Material qualifies for generation in the category. + * @param blockGenerator a function which accepts a Materials set to pack into a MetaBlock, and the ordinal this + * MetaBlock should have within its category. + * @return the number of blocks generated by this request + */ + @Deprecated + protected static int createGeneratedBlockDeprecated(Predicate materialPredicate, + BiConsumer blockGenerator) { + Material[] materialBuffer = new Material[16]; + Arrays.fill(materialBuffer, Materials._NULL); + int currentGenerationIndex = 0; + for (Material material : Material.MATERIAL_REGISTRY) { + if (materialPredicate.test(material)) { + if (currentGenerationIndex > 0 && currentGenerationIndex % 16 == 0) { + blockGenerator.accept(materialBuffer, currentGenerationIndex / 16 - 1); + Arrays.fill(materialBuffer, Materials._NULL); + } + materialBuffer[currentGenerationIndex % 16] = material; + currentGenerationIndex++; + } + } + if (materialBuffer[0] != Materials._NULL) { + blockGenerator.accept(materialBuffer, currentGenerationIndex / 16); + } + return (currentGenerationIndex / 16) + 1; + } +}