minecraft-src/net/minecraft/util/datafix/fixes/LeavesFix.java
2025-07-04 03:15:13 +03:00

375 lines
13 KiB
Java

package net.minecraft.util.datafix.fixes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFix;
import com.mojang.datafixers.DataFixUtils;
import com.mojang.datafixers.OpticFinder;
import com.mojang.datafixers.TypeRewriteRule;
import com.mojang.datafixers.Typed;
import com.mojang.datafixers.schemas.Schema;
import com.mojang.datafixers.types.Type;
import com.mojang.datafixers.types.templates.List.ListType;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Dynamic;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.util.datafix.PackedBitStorage;
import org.jetbrains.annotations.Nullable;
public class LeavesFix extends DataFix {
private static final int NORTH_WEST_MASK = 128;
private static final int WEST_MASK = 64;
private static final int SOUTH_WEST_MASK = 32;
private static final int SOUTH_MASK = 16;
private static final int SOUTH_EAST_MASK = 8;
private static final int EAST_MASK = 4;
private static final int NORTH_EAST_MASK = 2;
private static final int NORTH_MASK = 1;
private static final int[][] DIRECTIONS = new int[][]{{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}};
private static final int DECAY_DISTANCE = 7;
private static final int SIZE_BITS = 12;
private static final int SIZE = 4096;
static final Object2IntMap<String> LEAVES = DataFixUtils.make(new Object2IntOpenHashMap<>(), object2IntOpenHashMap -> {
object2IntOpenHashMap.put("minecraft:acacia_leaves", 0);
object2IntOpenHashMap.put("minecraft:birch_leaves", 1);
object2IntOpenHashMap.put("minecraft:dark_oak_leaves", 2);
object2IntOpenHashMap.put("minecraft:jungle_leaves", 3);
object2IntOpenHashMap.put("minecraft:oak_leaves", 4);
object2IntOpenHashMap.put("minecraft:spruce_leaves", 5);
});
static final Set<String> LOGS = ImmutableSet.of(
"minecraft:acacia_bark",
"minecraft:birch_bark",
"minecraft:dark_oak_bark",
"minecraft:jungle_bark",
"minecraft:oak_bark",
"minecraft:spruce_bark",
"minecraft:acacia_log",
"minecraft:birch_log",
"minecraft:dark_oak_log",
"minecraft:jungle_log",
"minecraft:oak_log",
"minecraft:spruce_log",
"minecraft:stripped_acacia_log",
"minecraft:stripped_birch_log",
"minecraft:stripped_dark_oak_log",
"minecraft:stripped_jungle_log",
"minecraft:stripped_oak_log",
"minecraft:stripped_spruce_log"
);
public LeavesFix(Schema outputSchema, boolean changesType) {
super(outputSchema, changesType);
}
@Override
protected TypeRewriteRule makeRule() {
Type<?> type = this.getInputSchema().getType(References.CHUNK);
OpticFinder<?> opticFinder = type.findField("Level");
OpticFinder<?> opticFinder2 = opticFinder.type().findField("Sections");
Type<?> type2 = opticFinder2.type();
if (!(type2 instanceof ListType)) {
throw new IllegalStateException("Expecting sections to be a list.");
} else {
Type<?> type3 = ((ListType)type2).getElement();
OpticFinder<?> opticFinder3 = DSL.typeFinder(type3);
return this.fixTypeEverywhereTyped(
"Leaves fix",
type,
typed -> typed.updateTyped(
opticFinder,
typedx -> {
int[] is = new int[]{0};
Typed<?> typed2 = typedx.updateTyped(
opticFinder2,
typedxx -> {
Int2ObjectMap<LeavesFix.LeavesSection> int2ObjectMap = new Int2ObjectOpenHashMap<>(
(Map<? extends Integer, ? extends LeavesFix.LeavesSection>)typedxx.getAllTyped(opticFinder3)
.stream()
.map(typedxxx -> new LeavesFix.LeavesSection(typedxxx, this.getInputSchema()))
.collect(Collectors.toMap(LeavesFix.Section::getIndex, leavesSection -> leavesSection))
);
if (int2ObjectMap.values().stream().allMatch(LeavesFix.Section::isSkippable)) {
return typedxx;
} else {
List<IntSet> list = Lists.<IntSet>newArrayList();
for (int i = 0; i < 7; i++) {
list.add(new IntOpenHashSet());
}
for (LeavesFix.LeavesSection leavesSection : int2ObjectMap.values()) {
if (!leavesSection.isSkippable()) {
for (int j = 0; j < 4096; j++) {
int k = leavesSection.getBlock(j);
if (leavesSection.isLog(k)) {
((IntSet)list.get(0)).add(leavesSection.getIndex() << 12 | j);
} else if (leavesSection.isLeaf(k)) {
int l = this.getX(j);
int m = this.getZ(j);
is[0] |= getSideMask(l == 0, l == 15, m == 0, m == 15);
}
}
}
}
for (int i = 1; i < 7; i++) {
IntSet intSet = (IntSet)list.get(i - 1);
IntSet intSet2 = (IntSet)list.get(i);
IntIterator intIterator = intSet.iterator();
while (intIterator.hasNext()) {
int l = intIterator.nextInt();
int m = this.getX(l);
int n = this.getY(l);
int o = this.getZ(l);
for (int[] js : DIRECTIONS) {
int p = m + js[0];
int q = n + js[1];
int r = o + js[2];
if (p >= 0 && p <= 15 && r >= 0 && r <= 15 && q >= 0 && q <= 255) {
LeavesFix.LeavesSection leavesSection2 = int2ObjectMap.get(q >> 4);
if (leavesSection2 != null && !leavesSection2.isSkippable()) {
int s = getIndex(p, q & 15, r);
int t = leavesSection2.getBlock(s);
if (leavesSection2.isLeaf(t)) {
int u = leavesSection2.getDistance(t);
if (u > i) {
leavesSection2.setDistance(s, t, i);
intSet2.add(getIndex(p, q, r));
}
}
}
}
}
}
}
return typedxx.updateTyped(opticFinder3, typedxxx -> int2ObjectMap.get(typedxxx.get(DSL.remainderFinder()).get("Y").asInt(0)).write(typedxxx));
}
}
);
if (is[0] != 0) {
typed2 = typed2.update(DSL.remainderFinder(), dynamic -> {
Dynamic<?> dynamic2 = DataFixUtils.orElse(dynamic.get("UpgradeData").result(), dynamic.emptyMap());
return dynamic.set("UpgradeData", dynamic2.set("Sides", dynamic.createByte((byte)(dynamic2.get("Sides").asByte((byte)0) | is[0]))));
});
}
return typed2;
}
)
);
}
}
public static int getIndex(int x, int y, int z) {
return y << 8 | z << 4 | x;
}
private int getX(int index) {
return index & 15;
}
private int getY(int index) {
return index >> 8 & 0xFF;
}
private int getZ(int index) {
return index >> 4 & 15;
}
public static int getSideMask(boolean west, boolean east, boolean north, boolean south) {
int i = 0;
if (north) {
if (east) {
i |= 2;
} else if (west) {
i |= 128;
} else {
i |= 1;
}
} else if (south) {
if (west) {
i |= 32;
} else if (east) {
i |= 8;
} else {
i |= 16;
}
} else if (east) {
i |= 4;
} else if (west) {
i |= 64;
}
return i;
}
public static final class LeavesSection extends LeavesFix.Section {
private static final String PERSISTENT = "persistent";
private static final String DECAYABLE = "decayable";
private static final String DISTANCE = "distance";
@Nullable
private IntSet leaveIds;
@Nullable
private IntSet logIds;
@Nullable
private Int2IntMap stateToIdMap;
public LeavesSection(Typed<?> typed, Schema schema) {
super(typed, schema);
}
@Override
protected boolean skippable() {
this.leaveIds = new IntOpenHashSet();
this.logIds = new IntOpenHashSet();
this.stateToIdMap = new Int2IntOpenHashMap();
for (int i = 0; i < this.palette.size(); i++) {
Dynamic<?> dynamic = (Dynamic<?>)this.palette.get(i);
String string = dynamic.get("Name").asString("");
if (LeavesFix.LEAVES.containsKey(string)) {
boolean bl = Objects.equals(dynamic.get("Properties").get("decayable").asString(""), "false");
this.leaveIds.add(i);
this.stateToIdMap.put(this.getStateId(string, bl, 7), i);
this.palette.set(i, this.makeLeafTag(dynamic, string, bl, 7));
}
if (LeavesFix.LOGS.contains(string)) {
this.logIds.add(i);
}
}
return this.leaveIds.isEmpty() && this.logIds.isEmpty();
}
private Dynamic<?> makeLeafTag(Dynamic<?> dynamic, String name, boolean persistent, int distance) {
Dynamic<?> dynamic2 = dynamic.emptyMap();
dynamic2 = dynamic2.set("persistent", dynamic2.createString(persistent ? "true" : "false"));
dynamic2 = dynamic2.set("distance", dynamic2.createString(Integer.toString(distance)));
Dynamic<?> dynamic3 = dynamic.emptyMap();
dynamic3 = dynamic3.set("Properties", dynamic2);
return dynamic3.set("Name", dynamic3.createString(name));
}
public boolean isLog(int id) {
return this.logIds.contains(id);
}
public boolean isLeaf(int id) {
return this.leaveIds.contains(id);
}
int getDistance(int index) {
return this.isLog(index) ? 0 : Integer.parseInt(((Dynamic)this.palette.get(index)).get("Properties").get("distance").asString(""));
}
void setDistance(int index, int block, int distance) {
Dynamic<?> dynamic = (Dynamic<?>)this.palette.get(block);
String string = dynamic.get("Name").asString("");
boolean bl = Objects.equals(dynamic.get("Properties").get("persistent").asString(""), "true");
int i = this.getStateId(string, bl, distance);
if (!this.stateToIdMap.containsKey(i)) {
int j = this.palette.size();
this.leaveIds.add(j);
this.stateToIdMap.put(i, j);
this.palette.add(this.makeLeafTag(dynamic, string, bl, distance));
}
int j = this.stateToIdMap.get(i);
if (1 << this.storage.getBits() <= j) {
PackedBitStorage packedBitStorage = new PackedBitStorage(this.storage.getBits() + 1, 4096);
for (int k = 0; k < 4096; k++) {
packedBitStorage.set(k, this.storage.get(k));
}
this.storage = packedBitStorage;
}
this.storage.set(index, j);
}
}
public abstract static class Section {
protected static final String BLOCK_STATES_TAG = "BlockStates";
protected static final String NAME_TAG = "Name";
protected static final String PROPERTIES_TAG = "Properties";
private final Type<Pair<String, Dynamic<?>>> blockStateType = DSL.named(References.BLOCK_STATE.typeName(), DSL.remainderType());
protected final OpticFinder<List<Pair<String, Dynamic<?>>>> paletteFinder = DSL.fieldFinder("Palette", DSL.list(this.blockStateType));
protected final List<Dynamic<?>> palette;
protected final int index;
@Nullable
protected PackedBitStorage storage;
public Section(Typed<?> data, Schema schema) {
if (!Objects.equals(schema.getType(References.BLOCK_STATE), this.blockStateType)) {
throw new IllegalStateException("Block state type is not what was expected.");
} else {
Optional<List<Pair<String, Dynamic<?>>>> optional = data.getOptional(this.paletteFinder);
this.palette = (List<Dynamic<?>>)optional.map(list -> (List)list.stream().map(Pair::getSecond).collect(Collectors.toList())).orElse(ImmutableList.of());
Dynamic<?> dynamic = data.get(DSL.remainderFinder());
this.index = dynamic.get("Y").asInt(0);
this.readStorage(dynamic);
}
}
protected void readStorage(Dynamic<?> data) {
if (this.skippable()) {
this.storage = null;
} else {
long[] ls = data.get("BlockStates").asLongStream().toArray();
int i = Math.max(4, DataFixUtils.ceillog2(this.palette.size()));
this.storage = new PackedBitStorage(i, 4096, ls);
}
}
public Typed<?> write(Typed<?> data) {
return this.isSkippable()
? data
: data.update(DSL.remainderFinder(), dynamic -> dynamic.set("BlockStates", dynamic.createLongList(Arrays.stream(this.storage.getRaw()))))
.set(
this.paletteFinder,
(List<Pair<String, Dynamic<?>>>)this.palette.stream().map(dynamic -> Pair.of(References.BLOCK_STATE.typeName(), dynamic)).collect(Collectors.toList())
);
}
public boolean isSkippable() {
return this.storage == null;
}
public int getBlock(int index) {
return this.storage.get(index);
}
protected int getStateId(String name, boolean persistent, int distance) {
return LeavesFix.LEAVES.get(name) << 5 | (persistent ? 16 : 0) | distance;
}
int getIndex() {
return this.index;
}
protected abstract boolean skippable();
}
}