Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial 16-bit Metadata Re-work #7

Merged
merged 9 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Add your dependencies here

dependencies {

api("com.github.GTNewHorizons:GTNHLib:0.2.3:dev")
}
23 changes: 14 additions & 9 deletions src/main/java/com/gtnewhorizons/neid/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ public class Constants {
/**
* The total number of bits used by a block's metadata
*/
public static final int BITS_PER_METADATA = 4;
public static final int BITS_PER_METADATA = 16;
public static final int VANILLA_BITS_PER_METADATA = 4;

public static final int METADATA_COUNT = 1 << BITS_PER_METADATA;
public static final int VANILLA_METADATA_COUNT = 1 << VANILLA_BITS_PER_METADATA;

/**
* MAX_BLOCK_ID is calculated to 1 less bit than the total bits per ID to accomodate signed values. The value is
* stored as 16 bits, but it is signed so the maximum ID we can actually use is 1 bit less. Vanilla minecraft
Expand All @@ -29,6 +32,9 @@ public class Constants {
public static final int BLOCK_ID_MASK = (1 << BITS_PER_ID) - 1;
public static final int VANILLA_BLOCK_ID_MASK = VANILLA_MAX_BLOCK_ID;

public static final int METADATA_MASK = (1 << BITS_PER_METADATA) - 1;
public static final int VANILLA_METADATA_MASK = (1 << VANILLA_BITS_PER_METADATA) - 1;

/**
* Number of block stored in a single EBS, this is the same as vanilla.
*/
Expand All @@ -37,29 +43,28 @@ public class Constants {
/**
* This number is the total bytes in an ExtendedBlockStorage. It is: LSB + MSB + Metadata + Skylight Data +
* Blocklight Data. In vanilla: 8 + 4 + 4 + 4 + 4 = 24 bits per block = 3 bytes per block * 4,096 blocks per EBS =
* 12288 bytes per EBS Our equation: 16 + 0 + 4 + 4 + 4 = 32 bits per block = 3.5 bytes per block * 4,096 blocks per
* EBS = 14336 bytes per EBS
* 12288 bytes per EBS Our equation: 16 + 0 + 16 + 4 + 4 = 40 bits per block = 5 bytes per block * 4,096 blocks per
* EBS = 20480 bytes per EBS
*/
public static final int BYTES_PER_EBS = 14336;
public static final int BYTES_PER_EBS = 20480;
public static final int VANILLA_BYTES_PER_EBS = 12288;

/**
* This number is the total bytes stored in a chunk. It is: Number of EBS in a chunk(16) * BYTES_PER_EBS +
* MAX_BIOME_ID(256) In vanilla: 16 * 12288 + 256 = 196864 Our equation: 16 * BYTES_PER_EBS(14336) + 256 = 229632
* MAX_BIOME_ID(256) In vanilla: 16 * 12288 + 256 = 196864 Our equation: 16 * BYTES_PER_EBS(20480) + 256 = 327936
*/
public static final int BYTES_PER_CHUNK = 229632;
public static final int BYTES_PER_CHUNK = 327936;
public static final int VANILLA_BYTES_PER_CHUNK = 196864;

/**
* This number is the total bytes stored in an EBS, minute the lighting data. It is: (LSB + MSB + Metadata) *
* BLOCKS_PER_EBS(4096) In vanilla: 8 + 4 + 4 = 16 bits per block = 2 bytes per block * 4096 = 8192 Our equation: 16
* + 0 + 4 = 20 bits per block = 2.5 bytes per block. We have to round up because we use a whole byte for the
* metadata + msb, despite not using the msb anymore. So 3 bytes per block * 4096 = 12288.
* + 0 + 16 = 20 bits per block = 4 bytes per block * 4096 = 16384.
*
* If you look at vanilla source code, this value will be 2048 because seemingly Vanilla handles less EBS per chunk?
* Unsure, but Forge ASM's this value to 8192, so that is what we are looking for to modify.
*/
public static final int BYTES_PER_EBS_MINUS_LIGHTING = 12288;
public static final int BYTES_PER_EBS_MINUS_LIGHTING = 16384;
public static final int VANILLA_BYTES_PER_EBS_MINUS_LIGHTING = 8192;

public static final int MAX_DATA_WATCHER_ID = 127;
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/com/gtnewhorizons/neid/NEID.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
package com.gtnewhorizons.neid;

import com.gtnewhorizon.gtnhlib.config.ConfigException;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;

@Mod(modid = "neid", name = "NotEnoughIDs", version = Tags.VERSION, dependencies = "after:battlegear2@[1.3.0,);")
@Mod(
modid = "neid",
name = "NotEnoughIDs",
version = Tags.VERSION,
dependencies = "after:battlegear2@[1.3.0,);" + " required-after:gtnhlib@[0.2.1,);")
public class NEID {

@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
try {
ConfigurationManager.registerConfig(NEIDConfig.class);
} catch (ConfigException e) {
throw new RuntimeException("Failed to register NotEnoughIDs config!");
}
}

}
43 changes: 17 additions & 26 deletions src/main/java/com/gtnewhorizons/neid/NEIDConfig.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
package com.gtnewhorizons.neid;

import java.io.File;

import net.minecraft.launchwrapper.Launch;
import net.minecraftforge.common.config.Configuration;
import com.gtnewhorizon.gtnhlib.config.Config;

/**
* The modid registered here is uppercased, this differs from the actual mod id in that the real one is lowercase. This
* is done because the old pre GTNHLib config file was named uppercase, so we're just keeping that.
*/
@Config(modid = "NEID", category = "neid")
public class NEIDConfig {

static Configuration config;
public static boolean catchUnregisteredBlocks;
public static boolean removeInvalidBlocks;
public static boolean postNeidWorldsSupport;
public static boolean extendDataWatcher;
@Config.Comment("Causes a crash when a block has not been registered(e.g. has an id of -1)")
public static boolean CatchUnregisteredBlocks = false;

@Config.Comment("Remove invalid (corrupted) blocks from the game.")
public static boolean RemoveInvalidBlocks = false;

@Config.Comment("If true, only blocks with IDs > 4095 will disappear after removing NEID. Metadatas outside of the range 0-15 will be set to 0.")
public static boolean PostNeidWorldsSupport = true;

@Config.Comment("Extend DataWatch IDs. Vanilla limit is 31, new limit is 127.")
public static boolean ExtendDataWatcher = false;

static {
NEIDConfig.config = new Configuration(new File(Launch.minecraftHome, "config/NEID.cfg"));
NEIDConfig.catchUnregisteredBlocks = NEIDConfig.config.getBoolean("CatchUnregisteredBlocks", "NEID", false, "");
NEIDConfig.removeInvalidBlocks = NEIDConfig.config
.getBoolean("RemoveInvalidBlocks", "NEID", false, "Remove invalid (corrupted) blocks from the game.");
NEIDConfig.postNeidWorldsSupport = NEIDConfig.config.getBoolean(
"PostNeidWorldsSupport",
"NEID",
true,
"If true, only blocks with IDs > 4095 will disappear after removing NEID.");
NEIDConfig.extendDataWatcher = NEIDConfig.config.getBoolean(
"ExtendDataWatcher",
"NEID",
false,
"Extend DataWatcher IDs. Vanilla limit is 31, new limit is 127.");
NEIDConfig.config.save();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public String[] getTargetClass() {

@Override
public void transform(final ClassNode cn, final boolean obfuscated) {
if (NEIDConfig.extendDataWatcher) {
if (NEIDConfig.ExtendDataWatcher) {
final MethodNode method = AsmUtil.findMethod(cn, Name.MFQM_preInit);
AsmUtil.modifyIntConstantInMethod(method, 31, 127);
}
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/gtnewhorizons/neid/mixins/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public enum Mixins {
"minecraft.MixinS24PacketBlockAction",
"minecraft.MixinS26PacketMapChunkBulk",
"minecraft.MixinItemInWorldManager",
"minecraft.MixinAnvilChunkLoader"
"minecraft.MixinAnvilChunkLoader",
"minecraft.MixinBlock"
).setApplyIf(() -> true)),
VANILLA_STARTUP_CLIENT(new Builder("Start Vanilla Client").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.CLIENT).setPhase(Phase.EARLY).addMixinClasses(
Expand All @@ -37,7 +38,7 @@ public enum Mixins {
VANILLA_STARTUP_DATAWATCHER(new Builder("Start Vanilla DataWatcher").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.BOTH).setPhase(Phase.EARLY).addMixinClasses(
"minecraft.MixinDataWatcher"
).setApplyIf(() -> NEIDConfig.extendDataWatcher));
).setApplyIf(() -> NEIDConfig.ExtendDataWatcher));
// spotless:on
private final List<String> mixinClasses;
private final List<TargetedMod> targetedMods;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gtnewhorizons.neid.mixins.early.minecraft;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;

Expand All @@ -23,11 +24,11 @@ public class MixinAnvilChunkLoader {
target = "Lnet/minecraft/nbt/NBTTagCompound;setByteArray(Ljava/lang/String;[B)V",
ordinal = 0),
require = 1)
private void neid$overrideSetByteArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
private void neid$overrideWriteLSBArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
@Local(ordinal = 0) ExtendedBlockStorage ebs) {
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
nbt.setByteArray("Blocks16", ebsMixin.getBlockData());
if (NEIDConfig.postNeidWorldsSupport) {
if (NEIDConfig.PostNeidWorldsSupport) {
final short[] data = ebsMixin.getBlock16BArray();
final byte[] lsbData = new byte[data.length];
byte[] msbData = null;
Expand Down Expand Up @@ -58,13 +59,45 @@ public class MixinAnvilChunkLoader {
}
}

@Redirect(
method = "writeChunkToNBT",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/nbt/NBTTagCompound;setByteArray(Ljava/lang/String;[B)V",
ordinal = 2),
require = 1)
private void neid$overrideWriteMetadataArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
@Local(ordinal = 0) ExtendedBlockStorage ebs) {
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
nbt.setByteArray("Data16", ebsMixin.getBlockMeta());
if (NEIDConfig.PostNeidWorldsSupport) {
final short[] data = ebsMixin.getBlock16BMetaArray();
final byte[] metaData = new byte[data.length / 2];
for (int i = 0; i < data.length; i += 2) {
int meta1 = data[i];
int meta2 = data[i + 1];

if (meta1 < 0 || meta1 > 15) {
meta1 = 0;
}
if (meta2 < 0 || meta2 > 15) {
meta2 = 0;
}

metaData[i / 2] = (byte) (meta2 << 4 | meta1);
final int meta = data[i];
}
nbt.setByteArray("Data", metaData);
}
}

@Redirect(
method = "readChunkFromNBT",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/chunk/storage/ExtendedBlockStorage;setBlockLSBArray([B)V"),
require = 1)
private void neid$overrideSetLSBArray(ExtendedBlockStorage ebs, byte[] oldbrokenbytes,
private void neid$overrideReadLSBArray(ExtendedBlockStorage ebs, byte[] oldbrokenbytes,
@Local(ordinal = 1) NBTTagCompound nbt) {
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
if (nbt.hasKey("Blocks16")) {
Expand Down Expand Up @@ -96,7 +129,31 @@ public class MixinAnvilChunkLoader {
target = "Lnet/minecraft/nbt/NBTTagCompound;hasKey(Ljava/lang/String;I)Z",
ordinal = 0),
require = 1)
private boolean neid$nukeMSBCheck(NBTTagCompound nbttagcompound1, String s, int i) {
private boolean neid$overrideReadMSBArray(NBTTagCompound nbttagcompound1, String s, int i) {
return false;
}

@Redirect(
method = "readChunkFromNBT",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/chunk/storage/ExtendedBlockStorage;setBlockMetadataArray(Lnet/minecraft/world/chunk/NibbleArray;)V"),
require = 1)
private void neid$overrideReadMetadataArray(ExtendedBlockStorage ebs, NibbleArray oldBrokenNibbleArray,
@Local(ordinal = 1) NBTTagCompound nbt) {
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
if (nbt.hasKey("Data16")) {
ebsMixin.setBlockMeta(nbt.getByteArray("Data16"), 0);
} else if (nbt.hasKey("Data")) {
final short[] out = ebsMixin.getBlock16BMetaArray();
final byte[] metaData = nbt.getByteArray("Data");
for (int i = 0; i < out.length; i += 2) {
final byte meta = metaData[i / 2];
out[i] = (short) (meta & 0xF);
out[i + 1] = (short) ((meta >> 4) & 0xF);
}
} else {
assert false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.gtnewhorizons.neid.mixins.early.minecraft;

import net.minecraft.block.Block;
import net.minecraft.init.Blocks;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;

import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;

/**
* This mixin exists to completely re-work the way that block harvestability is calculated. Forge adds this system
* on-top of vanilla, which creates a pre-populated array on each block with the size of the maximum metadata value. For
* instance, in vanilla we get a String array, and an int array both of size 16, pre-populated with null and -1,
* respectively. This is awful for scalability with adding more possible Blocks, and increasing the metadata to 16-bits
* from 4. This uproots the under-workings of that system to use a default and dynamic HashMaps for metadata specific
* values, so we only use the memory we need, because most things don't even take advantage of this system, especially
* based on block metadatas. The public methods of this system maintain API compatibility, so anything using those
* should still function perfectly the same. The only potential breakage is if something were to reflect/ASM/mixin with
* the private values/methods we're overwriting.
*/
@Mixin(Block.class)
public class MixinBlock {

@Unique
private String neid$defaultHarvestTool = null;

@Unique
private int neid$defaultHarvestLevel = -1;

@Unique
private Int2ObjectOpenHashMap<String> harvestToolMap = new Int2ObjectOpenHashMap<>();

@Unique
private Int2IntOpenHashMap harvestLevelMap = new Int2IntOpenHashMap();

@Shadow(remap = false)
private String[] harvestTool = null;

@Shadow(remap = false)
private int[] harvestLevel = null;

/**
* @author Cleptomania
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
* ASMing/mixin to this.
*/
@Overwrite(remap = false)
public void setHarvestLevel(String toolClass, int level) {
this.neid$defaultHarvestTool = toolClass;
this.neid$defaultHarvestLevel = level;
}

/**
* @author Cleptomania
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
* ASMing/mixin to this.
*/
@Overwrite(remap = false)
public void setHarvestLevel(String toolClass, int level, int metadata) {
this.harvestToolMap.put(metadata, toolClass);
this.harvestLevelMap.put(metadata, level);
}

/**
* @author Cleptomania
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
* ASMing/mixin to this.
*/
@Overwrite(remap = false)
public String getHarvestTool(int metadata) {
return this.harvestToolMap.getOrDefault(metadata, this.neid$defaultHarvestTool);
}

/**
* @author Cleptmania
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
* ASMing/mixin to this.
*/
@Overwrite(remap = false)
public int getHarvestLevel(int metadata) {
return this.harvestLevelMap.getOrDefault(metadata, this.neid$defaultHarvestLevel);
}

/**
* @author Cleptomania
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
* ASMing/mixin to this.
*/
@Overwrite(remap = false)
public boolean isToolEffective(String type, int metadata) {
Block self = ((Block) (Object) this);
if ("pickaxe".equals(type)
&& (self == Blocks.redstone_ore || self == Blocks.lit_redstone_ore || self == Blocks.obsidian))
return false;
String harvestTool = this.getHarvestTool(metadata);
if (harvestTool == null) return false;
return harvestTool.equals(type);
}

}
Loading
Loading