Skip to content

Commit b6221e4

Browse files
Merge pull request #7 from Cleptomania/metadatav2
Initial 16-bit Metadata Re-work
2 parents 21cfaf0 + 09bc926 commit b6221e4

14 files changed

+334
-56
lines changed

dependencies.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Add your dependencies here
22

33
dependencies {
4-
4+
api("com.github.GTNewHorizons:GTNHLib:0.2.3:dev")
55
}

src/main/java/com/gtnewhorizons/neid/Constants.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ public class Constants {
1111
/**
1212
* The total number of bits used by a block's metadata
1313
*/
14-
public static final int BITS_PER_METADATA = 4;
14+
public static final int BITS_PER_METADATA = 16;
1515
public static final int VANILLA_BITS_PER_METADATA = 4;
1616

17+
public static final int METADATA_COUNT = 1 << BITS_PER_METADATA;
18+
public static final int VANILLA_METADATA_COUNT = 1 << VANILLA_BITS_PER_METADATA;
19+
1720
/**
1821
* MAX_BLOCK_ID is calculated to 1 less bit than the total bits per ID to accomodate signed values. The value is
1922
* stored as 16 bits, but it is signed so the maximum ID we can actually use is 1 bit less. Vanilla minecraft
@@ -29,6 +32,9 @@ public class Constants {
2932
public static final int BLOCK_ID_MASK = (1 << BITS_PER_ID) - 1;
3033
public static final int VANILLA_BLOCK_ID_MASK = VANILLA_MAX_BLOCK_ID;
3134

35+
public static final int METADATA_MASK = (1 << BITS_PER_METADATA) - 1;
36+
public static final int VANILLA_METADATA_MASK = (1 << VANILLA_BITS_PER_METADATA) - 1;
37+
3238
/**
3339
* Number of block stored in a single EBS, this is the same as vanilla.
3440
*/
@@ -37,29 +43,28 @@ public class Constants {
3743
/**
3844
* This number is the total bytes in an ExtendedBlockStorage. It is: LSB + MSB + Metadata + Skylight Data +
3945
* Blocklight Data. In vanilla: 8 + 4 + 4 + 4 + 4 = 24 bits per block = 3 bytes per block * 4,096 blocks per EBS =
40-
* 12288 bytes per EBS Our equation: 16 + 0 + 4 + 4 + 4 = 32 bits per block = 3.5 bytes per block * 4,096 blocks per
41-
* EBS = 14336 bytes per EBS
46+
* 12288 bytes per EBS Our equation: 16 + 0 + 16 + 4 + 4 = 40 bits per block = 5 bytes per block * 4,096 blocks per
47+
* EBS = 20480 bytes per EBS
4248
*/
43-
public static final int BYTES_PER_EBS = 14336;
49+
public static final int BYTES_PER_EBS = 20480;
4450
public static final int VANILLA_BYTES_PER_EBS = 12288;
4551

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

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

6570
public static final int MAX_DATA_WATCHER_ID = 127;
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
package com.gtnewhorizons.neid;
22

3+
import com.gtnewhorizon.gtnhlib.config.ConfigException;
4+
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
5+
36
import cpw.mods.fml.common.Mod;
7+
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
48

5-
@Mod(modid = "neid", name = "NotEnoughIDs", version = Tags.VERSION, dependencies = "after:battlegear2@[1.3.0,);")
9+
@Mod(
10+
modid = "neid",
11+
name = "NotEnoughIDs",
12+
version = Tags.VERSION,
13+
dependencies = "after:battlegear2@[1.3.0,);" + " required-after:gtnhlib@[0.2.1,);")
614
public class NEID {
15+
16+
@Mod.EventHandler
17+
public void preInit(FMLPreInitializationEvent event) {
18+
try {
19+
ConfigurationManager.registerConfig(NEIDConfig.class);
20+
} catch (ConfigException e) {
21+
throw new RuntimeException("Failed to register NotEnoughIDs config!");
22+
}
23+
}
24+
725
}
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,24 @@
11
package com.gtnewhorizons.neid;
22

3-
import java.io.File;
4-
5-
import net.minecraft.launchwrapper.Launch;
6-
import net.minecraftforge.common.config.Configuration;
3+
import com.gtnewhorizon.gtnhlib.config.Config;
74

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

10-
static Configuration config;
11-
public static boolean catchUnregisteredBlocks;
12-
public static boolean removeInvalidBlocks;
13-
public static boolean postNeidWorldsSupport;
14-
public static boolean extendDataWatcher;
12+
@Config.Comment("Causes a crash when a block has not been registered(e.g. has an id of -1)")
13+
public static boolean CatchUnregisteredBlocks = false;
14+
15+
@Config.Comment("Remove invalid (corrupted) blocks from the game.")
16+
public static boolean RemoveInvalidBlocks = false;
17+
18+
@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.")
19+
public static boolean PostNeidWorldsSupport = true;
20+
21+
@Config.Comment("Extend DataWatch IDs. Vanilla limit is 31, new limit is 127.")
22+
public static boolean ExtendDataWatcher = false;
1523

16-
static {
17-
NEIDConfig.config = new Configuration(new File(Launch.minecraftHome, "config/NEID.cfg"));
18-
NEIDConfig.catchUnregisteredBlocks = NEIDConfig.config.getBoolean("CatchUnregisteredBlocks", "NEID", false, "");
19-
NEIDConfig.removeInvalidBlocks = NEIDConfig.config
20-
.getBoolean("RemoveInvalidBlocks", "NEID", false, "Remove invalid (corrupted) blocks from the game.");
21-
NEIDConfig.postNeidWorldsSupport = NEIDConfig.config.getBoolean(
22-
"PostNeidWorldsSupport",
23-
"NEID",
24-
true,
25-
"If true, only blocks with IDs > 4095 will disappear after removing NEID.");
26-
NEIDConfig.extendDataWatcher = NEIDConfig.config.getBoolean(
27-
"ExtendDataWatcher",
28-
"NEID",
29-
false,
30-
"Extend DataWatcher IDs. Vanilla limit is 31, new limit is 127.");
31-
NEIDConfig.config.save();
32-
}
3324
}

src/main/java/com/gtnewhorizons/neid/asm/transformer/MFQM.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public String[] getTargetClass() {
1717

1818
@Override
1919
public void transform(final ClassNode cn, final boolean obfuscated) {
20-
if (NEIDConfig.extendDataWatcher) {
20+
if (NEIDConfig.ExtendDataWatcher) {
2121
final MethodNode method = AsmUtil.findMethod(cn, Name.MFQM_preInit);
2222
AsmUtil.modifyIntConstantInMethod(method, 31, 127);
2323
}

src/main/java/com/gtnewhorizons/neid/mixins/Mixins.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public enum Mixins {
2525
"minecraft.MixinS24PacketBlockAction",
2626
"minecraft.MixinS26PacketMapChunkBulk",
2727
"minecraft.MixinItemInWorldManager",
28-
"minecraft.MixinAnvilChunkLoader"
28+
"minecraft.MixinAnvilChunkLoader",
29+
"minecraft.MixinBlock"
2930
).setApplyIf(() -> true)),
3031
VANILLA_STARTUP_CLIENT(new Builder("Start Vanilla Client").addTargetedMod(TargetedMod.VANILLA)
3132
.setSide(Side.CLIENT).setPhase(Phase.EARLY).addMixinClasses(
@@ -37,7 +38,7 @@ public enum Mixins {
3738
VANILLA_STARTUP_DATAWATCHER(new Builder("Start Vanilla DataWatcher").addTargetedMod(TargetedMod.VANILLA)
3839
.setSide(Side.BOTH).setPhase(Phase.EARLY).addMixinClasses(
3940
"minecraft.MixinDataWatcher"
40-
).setApplyIf(() -> NEIDConfig.extendDataWatcher));
41+
).setApplyIf(() -> NEIDConfig.ExtendDataWatcher));
4142
// spotless:on
4243
private final List<String> mixinClasses;
4344
private final List<TargetedMod> targetedMods;

src/main/java/com/gtnewhorizons/neid/mixins/early/minecraft/MixinAnvilChunkLoader.java

+61-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.gtnewhorizons.neid.mixins.early.minecraft;
22

33
import net.minecraft.nbt.NBTTagCompound;
4+
import net.minecraft.world.chunk.NibbleArray;
45
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
56
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
67

@@ -23,11 +24,11 @@ public class MixinAnvilChunkLoader {
2324
target = "Lnet/minecraft/nbt/NBTTagCompound;setByteArray(Ljava/lang/String;[B)V",
2425
ordinal = 0),
2526
require = 1)
26-
private void neid$overrideSetByteArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
27+
private void neid$overrideWriteLSBArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
2728
@Local(ordinal = 0) ExtendedBlockStorage ebs) {
2829
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
2930
nbt.setByteArray("Blocks16", ebsMixin.getBlockData());
30-
if (NEIDConfig.postNeidWorldsSupport) {
31+
if (NEIDConfig.PostNeidWorldsSupport) {
3132
final short[] data = ebsMixin.getBlock16BArray();
3233
final byte[] lsbData = new byte[data.length];
3334
byte[] msbData = null;
@@ -58,13 +59,45 @@ public class MixinAnvilChunkLoader {
5859
}
5960
}
6061

62+
@Redirect(
63+
method = "writeChunkToNBT",
64+
at = @At(
65+
value = "INVOKE",
66+
target = "Lnet/minecraft/nbt/NBTTagCompound;setByteArray(Ljava/lang/String;[B)V",
67+
ordinal = 2),
68+
require = 1)
69+
private void neid$overrideWriteMetadataArray(NBTTagCompound nbt, String s, byte[] oldbrokenbytes,
70+
@Local(ordinal = 0) ExtendedBlockStorage ebs) {
71+
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
72+
nbt.setByteArray("Data16", ebsMixin.getBlockMeta());
73+
if (NEIDConfig.PostNeidWorldsSupport) {
74+
final short[] data = ebsMixin.getBlock16BMetaArray();
75+
final byte[] metaData = new byte[data.length / 2];
76+
for (int i = 0; i < data.length; i += 2) {
77+
int meta1 = data[i];
78+
int meta2 = data[i + 1];
79+
80+
if (meta1 < 0 || meta1 > 15) {
81+
meta1 = 0;
82+
}
83+
if (meta2 < 0 || meta2 > 15) {
84+
meta2 = 0;
85+
}
86+
87+
metaData[i / 2] = (byte) (meta2 << 4 | meta1);
88+
final int meta = data[i];
89+
}
90+
nbt.setByteArray("Data", metaData);
91+
}
92+
}
93+
6194
@Redirect(
6295
method = "readChunkFromNBT",
6396
at = @At(
6497
value = "INVOKE",
6598
target = "Lnet/minecraft/world/chunk/storage/ExtendedBlockStorage;setBlockLSBArray([B)V"),
6699
require = 1)
67-
private void neid$overrideSetLSBArray(ExtendedBlockStorage ebs, byte[] oldbrokenbytes,
100+
private void neid$overrideReadLSBArray(ExtendedBlockStorage ebs, byte[] oldbrokenbytes,
68101
@Local(ordinal = 1) NBTTagCompound nbt) {
69102
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
70103
if (nbt.hasKey("Blocks16")) {
@@ -96,7 +129,31 @@ public class MixinAnvilChunkLoader {
96129
target = "Lnet/minecraft/nbt/NBTTagCompound;hasKey(Ljava/lang/String;I)Z",
97130
ordinal = 0),
98131
require = 1)
99-
private boolean neid$nukeMSBCheck(NBTTagCompound nbttagcompound1, String s, int i) {
132+
private boolean neid$overrideReadMSBArray(NBTTagCompound nbttagcompound1, String s, int i) {
100133
return false;
101134
}
135+
136+
@Redirect(
137+
method = "readChunkFromNBT",
138+
at = @At(
139+
value = "INVOKE",
140+
target = "Lnet/minecraft/world/chunk/storage/ExtendedBlockStorage;setBlockMetadataArray(Lnet/minecraft/world/chunk/NibbleArray;)V"),
141+
require = 1)
142+
private void neid$overrideReadMetadataArray(ExtendedBlockStorage ebs, NibbleArray oldBrokenNibbleArray,
143+
@Local(ordinal = 1) NBTTagCompound nbt) {
144+
IExtendedBlockStorageMixin ebsMixin = (IExtendedBlockStorageMixin) ebs;
145+
if (nbt.hasKey("Data16")) {
146+
ebsMixin.setBlockMeta(nbt.getByteArray("Data16"), 0);
147+
} else if (nbt.hasKey("Data")) {
148+
final short[] out = ebsMixin.getBlock16BMetaArray();
149+
final byte[] metaData = nbt.getByteArray("Data");
150+
for (int i = 0; i < out.length; i += 2) {
151+
final byte meta = metaData[i / 2];
152+
out[i] = (short) (meta & 0xF);
153+
out[i + 1] = (short) ((meta >> 4) & 0xF);
154+
}
155+
} else {
156+
assert false;
157+
}
158+
}
102159
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.gtnewhorizons.neid.mixins.early.minecraft;
2+
3+
import net.minecraft.block.Block;
4+
import net.minecraft.init.Blocks;
5+
6+
import org.spongepowered.asm.mixin.Mixin;
7+
import org.spongepowered.asm.mixin.Overwrite;
8+
import org.spongepowered.asm.mixin.Shadow;
9+
import org.spongepowered.asm.mixin.Unique;
10+
11+
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
12+
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
13+
14+
/**
15+
* This mixin exists to completely re-work the way that block harvestability is calculated. Forge adds this system
16+
* on-top of vanilla, which creates a pre-populated array on each block with the size of the maximum metadata value. For
17+
* instance, in vanilla we get a String array, and an int array both of size 16, pre-populated with null and -1,
18+
* respectively. This is awful for scalability with adding more possible Blocks, and increasing the metadata to 16-bits
19+
* from 4. This uproots the under-workings of that system to use a default and dynamic HashMaps for metadata specific
20+
* values, so we only use the memory we need, because most things don't even take advantage of this system, especially
21+
* based on block metadatas. The public methods of this system maintain API compatibility, so anything using those
22+
* should still function perfectly the same. The only potential breakage is if something were to reflect/ASM/mixin with
23+
* the private values/methods we're overwriting.
24+
*/
25+
@Mixin(Block.class)
26+
public class MixinBlock {
27+
28+
@Unique
29+
private String neid$defaultHarvestTool = null;
30+
31+
@Unique
32+
private int neid$defaultHarvestLevel = -1;
33+
34+
@Unique
35+
private Int2ObjectOpenHashMap<String> harvestToolMap = new Int2ObjectOpenHashMap<>();
36+
37+
@Unique
38+
private Int2IntOpenHashMap harvestLevelMap = new Int2IntOpenHashMap();
39+
40+
@Shadow(remap = false)
41+
private String[] harvestTool = null;
42+
43+
@Shadow(remap = false)
44+
private int[] harvestLevel = null;
45+
46+
/**
47+
* @author Cleptomania
48+
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
49+
* ASMing/mixin to this.
50+
*/
51+
@Overwrite(remap = false)
52+
public void setHarvestLevel(String toolClass, int level) {
53+
this.neid$defaultHarvestTool = toolClass;
54+
this.neid$defaultHarvestLevel = level;
55+
}
56+
57+
/**
58+
* @author Cleptomania
59+
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
60+
* ASMing/mixin to this.
61+
*/
62+
@Overwrite(remap = false)
63+
public void setHarvestLevel(String toolClass, int level, int metadata) {
64+
this.harvestToolMap.put(metadata, toolClass);
65+
this.harvestLevelMap.put(metadata, level);
66+
}
67+
68+
/**
69+
* @author Cleptomania
70+
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
71+
* ASMing/mixin to this.
72+
*/
73+
@Overwrite(remap = false)
74+
public String getHarvestTool(int metadata) {
75+
return this.harvestToolMap.getOrDefault(metadata, this.neid$defaultHarvestTool);
76+
}
77+
78+
/**
79+
* @author Cleptmania
80+
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
81+
* ASMing/mixin to this.
82+
*/
83+
@Overwrite(remap = false)
84+
public int getHarvestLevel(int metadata) {
85+
return this.harvestLevelMap.getOrDefault(metadata, this.neid$defaultHarvestLevel);
86+
}
87+
88+
/**
89+
* @author Cleptomania
90+
* @reason Support 16-bit metadata without using GBs of memory. Classdump of full GTNH shows nothing else
91+
* ASMing/mixin to this.
92+
*/
93+
@Overwrite(remap = false)
94+
public boolean isToolEffective(String type, int metadata) {
95+
Block self = ((Block) (Object) this);
96+
if ("pickaxe".equals(type)
97+
&& (self == Blocks.redstone_ore || self == Blocks.lit_redstone_ore || self == Blocks.obsidian))
98+
return false;
99+
String harvestTool = this.getHarvestTool(metadata);
100+
if (harvestTool == null) return false;
101+
return harvestTool.equals(type);
102+
}
103+
104+
}

0 commit comments

Comments
 (0)