Add FlatteningMap and EntityFlatteningMap (#1635)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-04-18 09:20:24 -07:00
1 parent e5cd1372e7
commit 0db691db0b
5 files changed
+1469 -71

No files matched your search

@@ -7,8 +7,10 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
import net.coderbot.iris.shaderpack.materialmap.FlatteningMap;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
@@ -39,8 +41,16 @@ public class BlockMaterialMapping {
Map<Block, BlockRenderLayer> blockTypeIds = new Reference2ReferenceOpenHashMap<>();
blockPropertiesMap.forEach((id, blockType) -> {
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
final Block block = Block.getBlockFromName(resourceLocation.toString());
Block block = resolveBlock(id);
// Try flattening map for modern names
if ((block == null || block == Blocks.air) && "minecraft".equals(id.getNamespace())) {
List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
if (legacyEntries != null) {
// Use the first entry for render type (render type applies per-block)
block = resolveBlock(legacyEntries.getFirst().getId());
}
}
if (block == null || block == Blocks.air) {
return;
@@ -73,19 +83,32 @@ public class BlockMaterialMapping {
* Based on Iris's addBlockStates method, adapted for 1.7.10 metadata system.
*/
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId) {
final NamespacedId id = entry.getId();
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
NamespacedId id = entry.getId();
String name = id.getName();
boolean hasStateProps = !entry.getStateProperties().isEmpty();
boolean hasExplicitMetas = !entry.getMetas().isEmpty();
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
if ("minecraft".equals(id.getNamespace()) && (hasStateProps || !hasExplicitMetas)) {
List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(name, entry.getStateProperties());
if (legacyEntries != null) {
for (BlockEntry legacy : legacyEntries) {
applyMetas(resolveBlock(legacy.getId()), legacy.getMetas(), idMap, intId);
}
return;
}
}
// If the block doesn't exist, by default the registry will return AIR. That probably isn't what we want.
// TODO: Assuming that Registry.BLOCK.getDefaultId() == "minecraft:air" here
// Fall back to registry with the entry's own metas
Block block = resolveBlock(id);
applyMetas(block, entry.getMetas(), idMap, intId);
}
// If the block doesn't exist, by default the registry will return AIR. That probably isn't what we want.
// TODO: Assuming that Registry.BLOCK.getDefaultId() == "minecraft:air" here
private static void applyMetas(Block block, Set<Integer> metas, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId) {
if (block == null || block == Blocks.air) {
return;
}
Set<Integer> metas = entry.getMetas();
Int2IntMap metaMap = idMap.get(block);
if (metaMap == null) {
metaMap = new Int2IntOpenHashMap();
@@ -94,15 +117,18 @@ public class BlockMaterialMapping {
}
if (metas.isEmpty()) {
// Add all metadata values (0-15) if there aren't any specific ones
for (int meta = 0; meta < 16; meta++) {
metaMap.putIfAbsent(meta, intId);
}
} else {
// Add only specific metadata values
for (int meta : metas) {
metaMap.putIfAbsent(meta, intId);
}
}
}
private static Block resolveBlock(NamespacedId id) {
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
return (Block) Block.blockRegistry.getObject(resourceLocation.toString());
}
}
@@ -8,8 +8,10 @@ import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
import net.coderbot.iris.shaderpack.materialmap.EntityFlatteningMap;
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
@@ -56,7 +58,8 @@ public class IdMap {
/**
* A set of render type overrides for specific blocks. Allows shader packs to move blocks to different render types.
*/
private Map<NamespacedId, BlockRenderType> blockRenderTypeMap;
@Getter
private Map<NamespacedId, BlockRenderType> blockRenderTypeMap;
IdMap(Path shaderPath, ShaderPackOptions shaderPackOptions, Iterable<StringPair> environmentDefines) {
// Check if block.properties has a dedicated 1.7.10 section
@@ -69,8 +72,8 @@ public class IdMap {
// Pack has a 1.7.10 section
resolvedDefines = environmentDefines;
loadProperties(shaderPath, "block.properties", shaderPackOptions, environmentDefines).ifPresent(blockProperties -> {
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
blockPropertiesMap = parseBlockMap(blockProperties);
blockRenderTypeMap = parseRenderTypeMap(blockProperties);
});
} else {
// No 1.7.10 section, use modern MC_VERSION and convert entries
@@ -87,8 +90,8 @@ public class IdMap {
resolvedDefines = modernDefines;
loadProperties(shaderPath, "block.properties", shaderPackOptions, modernDefines).ifPresent(blockProperties -> {
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
blockPropertiesMap = parseBlockMap(blockProperties);
blockRenderTypeMap = parseRenderTypeMap(blockProperties);
blockPropertiesMap = LegacyIdMap.convertModernBlockEntries(blockPropertiesMap);
});
}
@@ -141,7 +144,7 @@ public class IdMap {
private static String readProperties(Path shaderPath, String name) {
try {
// ID maps should be encoded in ISO_8859_1.
return new String(Files.readAllBytes(shaderPath.resolve(name)), StandardCharsets.ISO_8859_1);
return Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1);
} catch (NoSuchFileException e) {
Iris.logger.debug("An " + name + " file was not found in the current shaderpack");
@@ -158,7 +161,24 @@ public class IdMap {
}
private static Object2IntMap<NamespacedId> parseEntityIdMap(Properties properties) {
return parseIdMap(properties, "entity.", "entity.properties");
Object2IntMap<NamespacedId> idMap = parseIdMap(properties, "entity.", "entity.properties");
// For modern shader packs: translate modern entity names to 1.7.10 names
// so runtime lookups match EntityList's registered names directly.
Object2IntMap<NamespacedId> augmented = new Object2IntOpenHashMap<>(idMap);
augmented.defaultReturnValue(-1);
for (Object2IntMap.Entry<NamespacedId> entry : idMap.object2IntEntrySet()) {
NamespacedId id = entry.getKey();
if ("minecraft".equals(id.getNamespace())) {
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
if (legacyName != null) {
augmented.putIfAbsent(new NamespacedId(legacyName), entry.getIntValue());
}
}
}
return Object2IntMaps.unmodifiable(augmented);
}
/**
@@ -210,7 +230,7 @@ public class IdMap {
} else if (c == '"') {
inQuotes = !inQuotes;
} else if (Character.isWhitespace(c) && !inQuotes) {
if (current.length() > 0) {
if (!current.isEmpty()) {
result.add(current.toString());
current.setLength(0);
}
@@ -230,7 +250,7 @@ public class IdMap {
}
// Add final token
if (current.length() > 0) {
if (!current.isEmpty()) {
result.add(current.toString());
}
@@ -280,14 +300,14 @@ public class IdMap {
return Object2IntMaps.unmodifiable(idMap);
}
private static Int2ObjectMap<List<BlockEntry>> parseBlockMap(Properties properties, String keyPrefix, String fileName) {
private static Int2ObjectMap<List<BlockEntry>> parseBlockMap(Properties properties) {
Int2ObjectMap<List<BlockEntry>> entriesById = new Int2ObjectOpenHashMap<>();
properties.forEach((keyObject, valueObject) -> {
final String key = (String) keyObject;
StringBuilder value = new StringBuilder((String) valueObject);
if (!key.startsWith(keyPrefix)) {
if (!key.startsWith("block.")) {
// Not a valid line, ignore it
return;
}
@@ -295,10 +315,10 @@ public class IdMap {
final int intId;
try {
intId = Integer.parseInt(key.substring(keyPrefix.length()));
intId = Integer.parseInt(key.substring("block.".length()));
} catch (NumberFormatException e) {
// Not a valid property line
Iris.logger.warn("Failed to parse line in " + fileName + ": invalid key " + key);
Iris.logger.warn("Failed to parse line in " + "block.properties" + ": invalid key " + key);
return;
}
@@ -315,7 +335,7 @@ public class IdMap {
}
// Parse identifiers
for (String part : parseIdentifierList(value.toString(), fileName, key)) {
for (String part : parseIdentifierList(value.toString(), "block.properties", key)) {
if (part.isEmpty()) {
continue;
}
@@ -323,7 +343,7 @@ public class IdMap {
try {
entries.add(BlockEntry.parse(part));
} catch (Exception e) {
Iris.logger.warn("Unexpected error while parsing an entry from " + fileName + " for the key " + key + ":", e);
Iris.logger.warn("Unexpected error while parsing an entry from " + "block.properties" + " for the key " + key + ":", e);
}
}
@@ -338,29 +358,29 @@ public class IdMap {
*
* This feature is used by Chocapic v9 and Wisdom Shaders. Otherwise, it is a rarely-used feature.
*/
private static Map<NamespacedId, BlockRenderType> parseRenderTypeMap(Properties properties, String keyPrefix, String fileName) {
private static Map<NamespacedId, BlockRenderType> parseRenderTypeMap(Properties properties) {
Map<NamespacedId, BlockRenderType> overrides = new HashMap<>();
properties.forEach((keyObject, valueObject) -> {
String key = (String) keyObject;
String value = (String) valueObject;
if (!key.startsWith(keyPrefix)) {
if (!key.startsWith("layer.")) {
// Not a valid line, ignore it
return;
}
// Note: We have to remove the prefix "layer." because fromString expects "cutout", not "layer.cutout".
String keyWithoutPrefix = key.substring(keyPrefix.length());
String keyWithoutPrefix = key.substring("layer.".length());
BlockRenderType renderType = BlockRenderType.fromString(keyWithoutPrefix).orElse(null);
if (renderType == null) {
Iris.logger.warn("Failed to parse line in " + fileName + ": invalid block render type: " + key);
Iris.logger.warn("Failed to parse line in " + "block.properties" + ": invalid block render type: " + key);
return;
}
for (String part : parseIdentifierList(value, fileName, key)) {
for (String part : parseIdentifierList(value, "block.properties", key)) {
// Note: NamespacedId performs no validation on the content. That will need to be done by whatever is
// converting these things to ResourceLocations.
overrides.put(new NamespacedId(part), renderType);
@@ -382,11 +402,7 @@ public class IdMap {
return entityIdMap;
}
public Map<NamespacedId, BlockRenderType> getBlockRenderTypeMap() {
return blockRenderTypeMap;
}
@Override
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -6,7 +6,9 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -14,10 +16,16 @@ import java.util.Set;
public class BlockEntry {
private final NamespacedId id;
private final Set<Integer> metas;
private final Map<String, String> stateProperties;
public BlockEntry(NamespacedId id, Set<Integer> metas) {
public BlockEntry(NamespacedId id, Set<Integer> metas) {
this(id, metas, Collections.emptyMap());
}
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
this.id = id;
this.metas = metas;
this.stateProperties = stateProperties;
}
/**
@@ -39,60 +47,66 @@ public class BlockEntry {
return new BlockEntry(new NamespacedId("minecraft", entry), Collections.emptySet());
}
// Examples of what we'll accept
// Examples of what we accept:
// stone
// stone:0
// minecraft:stone:0
// minecraft:stone:0,1,2 # Theoretically valid, but I haven't seen any examples in actual shaders
// minecraft:stone:0,1,2
// minecraft:furnace:lit=true (blockstate property)
// minecraft:oak_log:axis=y (blockstate property)
// Examples of what we don't (Yet?) accept - Seems to be from MC 1.8+
// minecraft:farmland:moisture=0
// minecraft:farmland:moisture=1
// minecraft:double_plant:half=lower
// minecraft:double_plant:half=upper
// minecraft:grass:snowy=true
// minecraft:unpowered_comparator:powered=false
// Less trivial case: no metas involved, just a namespace
//
// The first term MUST be a valid ResourceLocation component
// The second term, if it is not numeric, must be a valid ResourceLocation component.
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))) {
// Less trivial case: no metas or state properties, just a namespace
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))
&& !splitStates[1].contains("=")) {
return new BlockEntry(new NamespacedId(splitStates[0], splitStates[1]), Collections.emptySet());
}
// Complex case: One or more states involved...
// Complex case: metas and/or state properties
final int statesStart;
final NamespacedId id;
if (StringUtils.isNumeric(splitStates[1].substring(0, 1))) {
// We have an entry of the form "stone:0"
if (splitStates.length == 2) {
// "stone:0" or "stone:lit=true"
statesStart = 1;
id = new NamespacedId("minecraft", splitStates[0]);
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
// "stone:0:something" or "stone:lit=true:something" — unlikely but handle it
statesStart = 1;
id = new NamespacedId("minecraft", splitStates[0]);
} else {
// We have an entry of the form "minecraft:stone:0"
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
statesStart = 2;
id = new NamespacedId(splitStates[0], splitStates[1]);
}
final Set<Integer> metas = new HashSet<>();
final Set<Integer> metas = new HashSet<>();
final Map<String, String> properties = new HashMap<>();
for (int index = statesStart; index < splitStates.length; index++) {
// Parse out one or more metadata ids
final String[] metaParts = splitStates[index].split(",");
String segment = splitStates[index];
for (String metaPart : metaParts) {
try {
metas.add(Integer.parseInt(metaPart));
} catch (NumberFormatException e) {
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but "+ splitStates[index] + " is not of that form!");
}
}
if (segment.contains("=")) {
// Blockstate property: key=value
for (String prop : segment.split(",")) {
int eq = prop.indexOf('=');
if (eq > 0 && eq < prop.length() - 1) {
properties.put(prop.substring(0, eq), prop.substring(eq + 1));
}
}
} else {
// Metadata IDs: comma-separated integers
for (String metaPart : segment.split(",")) {
try {
metas.add(Integer.parseInt(metaPart));
} catch (NumberFormatException e) {
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but " + segment + " is not of that form!");
}
}
}
}
return new BlockEntry(id, metas);
return new BlockEntry(id, metas, properties);
}
@Override
@@ -100,11 +114,11 @@ public class BlockEntry {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BlockEntry that = (BlockEntry) o;
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas);
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas) && Objects.equals(stateProperties, that.stateProperties);
}
@Override
public int hashCode() {
return Objects.hash(id, metas);
return Objects.hash(id, metas, stateProperties);
}
}
@@ -0,0 +1,95 @@
package net.coderbot.iris.shaderpack.materialmap;
import java.util.HashMap;
import java.util.Map;
/**
* Maps modern entity names to their 1.7.10 equivalents.
*
* In 1.11+, entity IDs were changed to namespaced: "minecraft:cave_spider".
* In 1.7.10, entities are registered with names like "CaveSpider".
* Some entities were also renamed or split into multiple types.
*
* This map is used at lookup time: when a modern shader pack references an entity by its
* modern name, we resolve it to the 1.7.10 name that EntityList uses.
*/
public class EntityFlatteningMap {
/** Maps modern entity name -> 1.7.10 entity name. */
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
static {
// Simple case changes
simple("Arrow", "arrow");
simple("Bat", "bat");
simple("Blaze", "blaze");
simple("Boat", "boat");
simple("Chicken", "chicken");
simple("Cow", "cow");
simple("Creeper", "creeper");
simple("Enderman", "enderman");
simple("Ghast", "ghast");
simple("Giant", "giant");
simple("Item", "item");
simple("Painting", "painting");
simple("Pig", "pig");
simple("Sheep", "sheep");
simple("Silverfish", "silverfish");
simple("Skeleton", "skeleton");
simple("Slime", "slime");
simple("Snowball", "snowball");
simple("Spider", "spider");
simple("Squid", "squid");
simple("Villager", "villager");
simple("Witch", "witch");
simple("Wolf", "wolf");
simple("Zombie", "zombie");
// Renamed entities
simple("CaveSpider", "cave_spider");
simple("EnderCrystal", "end_crystal");
simple("EnderDragon", "ender_dragon");
simple("EntityHorse", "horse");
simple("EyeOfEnderSignal", "eye_of_ender");
simple("FallingSand", "falling_block");
simple("Fireball", "fireball");
simple("FireworksRocketEntity", "firework_rocket");
simple("ItemFrame", "item_frame");
simple("LavaSlime", "magma_cube");
simple("LeashKnot", "leash_knot");
simple("MushroomCow", "mooshroom");
simple("Ozelot", "ocelot");
simple("PigZombie", "zombified_piglin");
simple("PrimedTnt", "tnt");
simple("SmallFireball", "small_fireball");
simple("SnowMan", "snow_golem");
simple("ThrownEnderpearl", "ender_pearl");
simple("ThrownExpBottle", "experience_bottle");
simple("ThrownPotion", "potion");
simple("VillagerGolem", "iron_golem");
simple("WitherBoss", "wither");
simple("WitherSkull", "wither_skull");
simple("XPOrb", "experience_orb");
// Minecarts
simple("MinecartRideable", "minecart");
simple("MinecartChest", "chest_minecart");
simple("MinecartCommandBlock", "command_block_minecart");
simple("MinecartFurnace", "furnace_minecart");
simple("MinecartHopper", "hopper_minecart");
simple("MinecartSpawner", "spawner_minecart");
simple("MinecartTNT", "tnt_minecart");
}
/**
* Returns the 1.7.10 entity name for a modern entity name, or null if no mapping exists.
* The input name should not include the "minecraft:" namespace prefix.
*/
public static String toLegacy(String modernName) {
return MODERN_TO_LEGACY.get(modernName);
}
private static void simple(String legacy, String modern) {
MODERN_TO_LEGACY.put(modern, legacy);
}
}
File diff suppressed because it is too large. Load diff