375 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			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<?> data, Schema schema) {
 | |
| 			super(data, 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();
 | |
| 	}
 | |
| }
 |