19 Commits
Author SHA1 Message Date
FalsePattern a3165a0cca fix: crash when joining server due to buggy thermos compat patch 2026-03-07 16:11:32 +01:00
rjnasers 3c4ffe502c Actually fiixng it 2026-03-07 15:48:06 +01:00
rjnasers 68f297faba Merge branch 'master' of https://github.com/Cardinalstars/ChunkAPI-Cardinalstar 2026-03-07 15:22:21 +01:00
rjnasers 02f2321339 Fixing really stupid mistake I made 2026-03-07 15:22:15 +01:00
FalsePattern 220755b3f3 feat: Thermos compat
Closes: https://github.com/LegacyModdingMC/ChunkAPI/pull/22
2026-02-06 20:54:23 +01:00
rjnasers 5be9cb6ad7 Formatting 2026-01-27 16:15:10 +01:00
rjnasers 18e590236b Separating mixin implementations to be more DRY 2026-01-27 16:13:36 +01:00
rjnasers c40cebaa90 copyright 2026-01-27 15:44:34 +01:00
rjnasers 7efe0cbcf4 Some cleanup 2026-01-27 14:00:20 +01:00
rjnasers d00d40e0e4 A start 2026-01-24 01:18:05 +01:00
FalsePattern 01de5f589a Merge remote-tracking branch 'pr18/cc-compat' 2026-01-23 20:04:36 +01:00
RecursivePineapple 023d9c96ee Cubic Chunks Compatibility 2025-11-26 11:06:09 -05:00
FalsePattern b94617d502 feat: Spool compat 2025-10-16 15:35:27 +02:00
FalsePattern 66365292a4 docs: Remove fplib dep in readme 2025-10-16 15:32:26 +02:00
FalsePattern 98cb70ab42 chore: Port to V2 mixins 2025-10-16 15:32:17 +02:00
FalsePattern 3da2da2846 build: Update foojay resolver 2025-10-16 15:18:15 +02:00
FalsePattern f1a2c011e2 build: Add gradle caching 2025-10-16 15:17:42 +02:00
FalsePattern bd9a1d1f81 build: Update gradle and buildscript 2025-09-27 20:54:17 +02:00
FalsePattern 5d44d49968 feat: Ordered data managers 2025-09-27 20:53:20 +02:00
40 changed files with 915 additions and 599 deletions

No files matched your search

+1 -2
View File
@@ -1,5 +1,5 @@
plugins {
id("com.falsepattern.fpgradle-mc") version("0.18.0")
id("com.falsepattern.fpgradle-mc") version("2.1.0")
}
group = "com.falsepattern"
@@ -17,7 +17,6 @@ minecraft_fp {
mixin {
pkg = "internal.mixin.mixins"
pluginClass = "internal.mixin.plugin.MixinPlugin"
}
core {
+2
View File
@@ -0,0 +1,2 @@
org.gradle.caching=true
org.gradle.configuration-cache=true
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -4
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
Vendored
+1 -2
View File
@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
-1
View File
@@ -117,5 +117,4 @@ Manager data:
Dependencies
------------
[FalsePatternLib](https://github.com/Falsepattern/FalsePatternLib)
[UniMixins](https://github.com/LegacyModdingMC/UniMixins)
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0")
}
rootProject.name = "ChunkAPI"
@@ -22,6 +22,7 @@
package com.falsepattern.chunk.api;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -84,6 +85,9 @@ public interface DataManager {
* Serializes your data into a packet.
*
* @param chunk The chunk to serialize.
* @param subChunkMask The mask that controls which subchunks need to be serialized. This will be 0 when cubic chunks is
* present - only chunk-specific information (like biomes) should be sent in this case.
* @param forceUpdate True when the chunk is first synced.
*/
@Contract(mutates = "param4")
void writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, ByteBuffer buffer);
@@ -91,13 +95,55 @@ public interface DataManager {
/**
* Deserializes your data from a packet.
*
* @param chunk The chunk to deserialize.
* @param buffer The packet buffer to read from.
* @param chunk The chunk to deserialize.
* @param subChunkMask The mask that controls which subchunks need to be deserialized. This will be 0 when cubic chunks is
* present - only chunk-specific information (like biomes) should be sent in this case.
* @param forceUpdate True when the chunk is first synced.
*/
@Contract(mutates = "param1,param4")
void readFromBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, ByteBuffer buffer);
}
/**
* A cube-specific variant of {@link PacketDataManager}. This is only used by cubic chunks.
*
* @author RecursivePineapple
* @since 0.7.1
* @apiNote Cubes, subchunks, and ExtendedBlockStorages are equivalent. A cube is a subchunk and a cube contains an EBS.
* To keep dependencies simple, cube-specific information is not available to this interface, but a cube's location can be
* retrieved by combining the chunk location and the EBS Y level. Note that the EBS's Y level can be <0 and >= 16.
*/
@ApiStatus.Experimental
interface CubicPacketDataManager extends DataManager {
/**
* @return The maximum amount of bytes your data can take up in a packet.
*
* @implSpec This is used to determine the size of the packet compression/decompression buffer.
* Only called ONCE, during registration!
*/
@Contract(pure = true)
int maxPacketSizeCubic();
/**
* Serializes your data into a packet.
*
* @param chunk The chunk that contains the subchunk.
* @param blockStorage The subchunk (cube) that was updated.
*/
@Contract(mutates = "param3")
void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer);
/**
* Deserializes your data from a packet.
*
* @param chunk The chunk that contains the subchunk.
* @param blockStorage The subchunk (cube) that was updated.
*/
@Contract(mutates = "param1,param2,param3")
void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer);
}
/**
* Implement this interface if you additionally want to synchronize your data on single and multi-block updates,
* not just chunk updates.
@@ -31,6 +31,7 @@ import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import java.util.Set;
import java.util.SortedSet;
/**
* This is an API class covered by the additional permissions in the license.
@@ -48,12 +49,36 @@ public class DataRegistry {
* Registers a ChunkDataManager. Only do this during the init phase.
*
* @param manager The manager to register.
* @param ordering The natural ordering index for the data manager when iterating the list of managers.
* <p>
* ChunkAPI, Lumi, RPLE, and EndlessIDs all use 0. Negative numbers are sorted earlier. Positive numbers are sorted later.
* <p>
* Use 0 unless you specifically need to do something later.
* <p>
* As a convention, do this in increments of 1000 so that other people can order "between" your manager and the base managers.
*
*
* @throws IllegalStateException If the registration stage is over.
* @throws IllegalArgumentException If the manager has a duplicate id.
*/
public static void registerDataManager(DataManager manager, int ordering) throws IllegalStateException, IllegalArgumentException {
DataRegistryImpl.registerDataManager(manager, ordering);
}
/**
* Registers a ChunkDataManager. Only do this during the init phase.
* Has an implicit ordering index of 0.
*
* @deprecated Use {@link #registerDataManager(DataManager, int)} with an explicit ordering.
*
* @param manager The manager to register.
*
* @throws IllegalStateException If the registration stage is over.
* @throws IllegalArgumentException If the manager has a duplicate id.
*/
@Deprecated
public static void registerDataManager(DataManager manager) throws IllegalStateException, IllegalArgumentException {
DataRegistryImpl.registerDataManager(manager);
DataRegistryImpl.registerDataManager(manager, 0);
}
/**
@@ -72,10 +97,22 @@ public class DataRegistry {
* The id of a manager is its domain and id separated by a colon. (domain:id)
*/
@Contract(pure = true)
@Deprecated
public static @Unmodifiable Set<String> getRegisteredManagers() {
return DataRegistryImpl.getRegisteredManagers();
}
/**
* Returns an unmodifiable set of all registered ChunkDataManagers.
* The id of a manager is its domain and id separated by a colon. (domain:id)
* <p>
* This function also includes the ordering index of each manager.
*/
@Contract(pure = true)
public static @Unmodifiable SortedSet<OrderedManager> getRegisteredManagersOrdered() {
return DataRegistryImpl.getRegisteredManagersOrdered();
}
/**
* Copies chunk-level data from a source chunk to a target chunk.
* DOES NOT copy data contained inside its ExtendedBlockStorage instances!!
@@ -20,26 +20,36 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin;
package com.falsepattern.chunk.api;
import com.falsepattern.chunk.internal.Tags;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.IMixin;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.IMixinPlugin;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.ITargetedMod;
import lombok.Getter;
import org.apache.logging.log4j.Logger;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class MixinPlugin implements IMixinPlugin {
@Getter
private final Logger logger = IMixinPlugin.createLogger(Tags.MOD_NAME);
import java.util.Objects;
@RequiredArgsConstructor
public class OrderedManager implements Comparable<OrderedManager> {
public final int ordering;
public final @NotNull String id;
@Override
public ITargetedMod[] getTargetedModEnumValues() {
return TargetedMod.values();
public int compareTo(OrderedManager o) {
int ord = Integer.compare(ordering, o.ordering);
if (ord != 0)
return ord;
return id.compareTo(o.id);
}
@Override
public IMixin[] getMixinEnumValues() {
return Mixin.values();
public boolean equals(Object obj) {
if (!(obj instanceof OrderedManager))
return false;
val other = (OrderedManager) obj;
return id.equals(other.id) && ordering == other.ordering;
}
@Override
public int hashCode() {
return Objects.hash(ordering, id);
}
}
@@ -41,11 +41,11 @@ public class ChunkAPI {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
DataRegistry.registerDataManager(new BlockIDManager());
DataRegistry.registerDataManager(new MetadataManager());
DataRegistry.registerDataManager(new LightingManager());
DataRegistry.registerDataManager(new BlocklightManager());
DataRegistry.registerDataManager(new SkylightManager());
DataRegistry.registerDataManager(new BiomeManager());
DataRegistry.registerDataManager(new BlockIDManager(), 0);
DataRegistry.registerDataManager(new MetadataManager(), 0);
DataRegistry.registerDataManager(new LightingManager(), 0);
DataRegistry.registerDataManager(new BlocklightManager(), 0);
DataRegistry.registerDataManager(new SkylightManager(), 0);
DataRegistry.registerDataManager(new BiomeManager(), 0);
}
}
@@ -23,6 +23,7 @@
package com.falsepattern.chunk.internal;
import com.falsepattern.chunk.api.DataManager;
import com.falsepattern.chunk.api.OrderedManager;
import lombok.Data;
import lombok.val;
import lombok.var;
@@ -48,17 +49,24 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
public class DataRegistryImpl {
private static final Set<String> managers = new HashSet<>();
private static final Map<String, PacketManagerInfo> packetManagers = new HashMap<>();
private static final Map<String, DataManager.BlockPacketDataManager> blockPacketManagers = new HashMap<>();
private static final Map<String, DataManager.StorageDataManager> NBTManagers = new HashMap<>();
private static final Map<String, DataManager.ChunkDataManager> chunkNBTManagers = new HashMap<>();
private static final Map<String, DataManager.SubChunkDataManager> subChunkNBTManagers = new HashMap<>();
private static final Map<String, OrderedManager> managersUnordered = new HashMap<>();
private static final SortedSet<OrderedManager> managers = new TreeSet<>();
private static final DualMap<PacketManagerInfo> packetManagers = new DualMap<>();
private static final DualMap<CubicPacketManagerInfo> cubicPacketManagers = new DualMap<>();
private static final DualMap<DataManager.BlockPacketDataManager> blockPacketManagers = new DualMap<>();
private static final DualMap<DataManager.StorageDataManager> NBTManagers = new DualMap<>();
private static final SortedMap<OrderedManager, DataManager.ChunkDataManager> chunkNBTManagers = new TreeMap<>();
private static final SortedMap<OrderedManager, DataManager.SubChunkDataManager> subChunkNBTManagers = new TreeMap<>();
private static final Set<String> disabledManagers = new HashSet<>();
private static int maxPacketSize = 4;
private static int maxPacketSizeCubic = 4;
@Data
private static class PacketManagerInfo {
@@ -66,12 +74,18 @@ public class DataRegistryImpl {
public final DataManager.PacketDataManager manager;
}
public static void registerDataManager(DataManager manager) throws IllegalStateException, IllegalArgumentException {
@Data
private static class CubicPacketManagerInfo {
public final int maxPacketSize;
public final DataManager.CubicPacketDataManager manager;
}
public static void registerDataManager(DataManager manager, int ordering) throws IllegalStateException, IllegalArgumentException {
if (Loader.instance().getLoaderState() != LoaderState.INITIALIZATION) {
throw new IllegalStateException("ChunkDataManager registration is not allowed at this time! Please register your ChunkDataManager in the init phase.");
}
var id = manager.domain() + ":" + manager.id();
if (managers.contains(id)) {
if (managersUnordered.containsKey(id)) {
throw new IllegalArgumentException("ChunkDataManager " + manager + " has a duplicate id!");
}
@@ -80,24 +94,33 @@ public class DataRegistryImpl {
return;
}
managers.add(id);
val ord = new OrderedManager(ordering, id);
managersUnordered.put(id, ord);
managers.add(ord);
if (manager instanceof DataManager.PacketDataManager) {
val packetManager = (DataManager.PacketDataManager) manager;
val maxSize = packetManager.maxPacketSize();
maxPacketSize += 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + maxSize;
packetManagers.put(id, new PacketManagerInfo(maxSize, packetManager));
val man = new PacketManagerInfo(maxSize, packetManager);
packetManagers.put(ord, man);
}
if (manager instanceof DataManager.BlockPacketDataManager) {
val blockPacketManager = (DataManager.BlockPacketDataManager) manager;
blockPacketManagers.put(id, blockPacketManager);
blockPacketManagers.put(ord, blockPacketManager);
}
if (manager instanceof DataManager.CubicPacketDataManager) {
val cubicPacketManager = (DataManager.CubicPacketDataManager) manager;
val maxSize = cubicPacketManager.maxPacketSizeCubic();
maxPacketSizeCubic += 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + maxSize;
cubicPacketManagers.put(ord, new CubicPacketManagerInfo(maxSize, cubicPacketManager));
}
if (manager instanceof DataManager.StorageDataManager) {
NBTManagers.put(id, (DataManager.StorageDataManager) manager);
NBTManagers.put(ord, (DataManager.StorageDataManager) manager);
if (manager instanceof DataManager.ChunkDataManager) {
chunkNBTManagers.put(id, (DataManager.ChunkDataManager) manager);
chunkNBTManagers.put(ord, (DataManager.ChunkDataManager) manager);
}
if (manager instanceof DataManager.SubChunkDataManager) {
subChunkNBTManagers.put(id, (DataManager.SubChunkDataManager) manager);
subChunkNBTManagers.put(ord, (DataManager.SubChunkDataManager) manager);
}
}
}
@@ -109,17 +132,23 @@ public class DataRegistryImpl {
Common.LOG.debug("Disabling ChunkDataManager " + id + " in domain " + domain + ". See the stacktrace for the source of this event.\nThis is NOT an error.",
new Throwable());
val manager = domain + ":" + id;
val ord = managersUnordered.remove(manager);
//Remove the manager from the list of managers, if it exists
if (managers.remove(manager)) {
if (ord != null) {
managers.remove(ord);
//Clear the maps
if (packetManagers.containsKey(manager)) {
val removed = packetManagers.remove(manager);
if (packetManagers.containsKey(ord)) {
val removed = packetManagers.remove(ord);
maxPacketSize -= 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + removed.maxPacketSize;
}
blockPacketManagers.remove(manager);
chunkNBTManagers.remove(manager);
subChunkNBTManagers.remove(manager);
NBTManagers.remove(manager);
if (cubicPacketManagers.containsKey(ord)) {
val removed = cubicPacketManagers.remove(ord);
maxPacketSizeCubic -= 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + removed.maxPacketSize;
}
blockPacketManagers.remove(ord);
chunkNBTManagers.remove(ord);
subChunkNBTManagers.remove(ord);
NBTManagers.remove(ord);
}
//Add the manager to the list of disabled managers, in case it gets registered after this disable call.
@@ -130,6 +159,10 @@ public class DataRegistryImpl {
return maxPacketSize;
}
public static int maxPacketSizeCubic() {
return maxPacketSizeCubic;
}
private static void writeString(ByteBuffer buffer, String string) {
val bytes = string.getBytes();
buffer.putInt(bytes.length);
@@ -166,14 +199,37 @@ public class DataRegistryImpl {
}
}
public static void readFromBufferCubic(Chunk chunk, ExtendedBlockStorage blockStorage, byte[] data) {
val buf = ByteBuffer.wrap(data);
buf.order(ByteOrder.LITTLE_ENDIAN);
int count = buf.getInt();
for (int i = 0; i < count; i++) {
val id = readString(buf);
val length = buf.getInt();
val managerInfo = cubicPacketManagers.get(id);
if (managerInfo == null) {
Common.LOG.error("Received data for unknown CubicPacketDataManager " + id + ". Skipping.");
buf.position(buf.position() + length);
continue;
}
if (length > managerInfo.maxPacketSize) {
Common.LOG.error("Received packet larger than max size for CubicPacketDataManager " + id + "! Continuing anyways, things might break!");
}
int start = buf.position();
val slice = createSlice(buf, start, length);
managerInfo.manager.readFromBuffer(chunk, blockStorage, slice);
buf.position(start + length);
}
}
public static int writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, byte[] data) {
val buf = ByteBuffer.wrap(data);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(packetManagers.size());
for (val pair : packetManagers.entrySet()) {
val id = pair.getKey();
val ord = pair.getKey();
val managerInfo = pair.getValue();
writeString(buf, id);
writeString(buf, ord.id);
int start = buf.position() + 4;
val slice = createSlice(buf, start, managerInfo.maxPacketSize);
managerInfo.manager.writeToBuffer(chunk, subChunkMask, forceUpdate, slice);
@@ -184,6 +240,24 @@ public class DataRegistryImpl {
return buf.position();
}
public static int writeToBufferCubic(Chunk chunk, ExtendedBlockStorage blockStorage, byte[] data) {
val buf = ByteBuffer.wrap(data);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(cubicPacketManagers.size());
for (val pair : cubicPacketManagers.entrySet()) {
val ord = pair.getKey();
val managerInfo = pair.getValue();
writeString(buf, ord.id);
int start = buf.position() + 4;
val slice = createSlice(buf, start, managerInfo.maxPacketSize);
managerInfo.manager.writeToBuffer(chunk, blockStorage, slice);
int length = slice.position();
buf.putInt(length);
buf.position(start + length);
}
return buf.position();
}
public static void writeBlockToPacket(Chunk chunk, int x, int y, int z, S23PacketBlockChange packet) {
for (val manager : blockPacketManagers.values()) {
manager.writeBlockToPacket(chunk, x, y, z, packet);
@@ -199,9 +273,9 @@ public class DataRegistryImpl {
public static void writeBlockPacketToBuffer(S23PacketBlockChange packet, PacketBuffer buffer) throws IOException {
buffer.writeInt(blockPacketManagers.size());
for (val pair : blockPacketManagers.entrySet()) {
val id = pair.getKey();
val ord = pair.getKey();
val manager = pair.getValue();
buffer.writeStringToBuffer(id);
buffer.writeStringToBuffer(ord.id);
manager.writeBlockPacketToBuffer(packet, buffer);
}
}
@@ -300,7 +374,11 @@ public class DataRegistryImpl {
}
public static Set<String> getRegisteredManagers() {
return Collections.unmodifiableSet(managers);
return Collections.unmodifiableSet(managersUnordered.keySet());
}
public static SortedSet<OrderedManager> getRegisteredManagersOrdered() {
return Collections.unmodifiableSortedSet(managers);
}
public static void readLevelDat(NBTTagCompound tag) {
@@ -349,7 +427,9 @@ public class DataRegistryImpl {
StringBuilder builder = new StringBuilder();
val saveManagers = readManagers(tag);
val removedManagers = new HashSet<>(saveManagers.keySet());
removedManagers.removeAll(NBTManagers.keySet());
for (val nbtManager: NBTManagers.keySet()) {
removedManagers.remove(nbtManager.id);
}
if (!removedManagers.isEmpty()) {
compatWarning = true;
builder.append("\nThe following data managers are no longer present:\n");
@@ -370,8 +450,8 @@ public class DataRegistryImpl {
}
val addedManagers = NBTManagers.keySet()
.stream()
.filter(manager -> !manager.startsWith("minecraft:"))
.filter(manager -> !saveManagers.containsKey(manager))
.filter(manager -> !manager.id.startsWith("minecraft:"))
.filter(manager -> !saveManagers.containsKey(manager.id))
.collect(Collectors.toSet());
if (!addedManagers.isEmpty()) {
compatWarning = true;
@@ -417,12 +497,12 @@ public class DataRegistryImpl {
tag.setTag("managers", managers);
tag.setString("version", Tags.MOD_VERSION);
for (val manager : NBTManagers.entrySet()) {
val name = manager.getKey();
if (name.startsWith("minecraft:")) {
val ord = manager.getKey();
if (ord.id.startsWith("minecraft:")) {
continue;
}
val value = manager.getValue();
managers.setTag(name, SaveManagerInfo.fromManager(value).toNBT());
managers.setTag(ord.id, SaveManagerInfo.fromManager(value).toNBT());
}
}
@@ -435,7 +515,6 @@ public class DataRegistryImpl {
return managers;
}
val managerTag = tag.getCompoundTag("managers");
//noinspection unchecked
for (val key : managerTag.func_150296_c()) {
managers.put(key, SaveManagerInfo.fromNBT(managerTag.getCompoundTag(key)));
}
@@ -0,0 +1,86 @@
/*
* ChunkAPI
*
* Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal;
import com.falsepattern.chunk.api.OrderedManager;
import lombok.val;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class DualMap<T> {
private final Map<String, T> unordered = new HashMap<>();
private final SortedMap<OrderedManager, T> ordered = new TreeMap<>();
public void put(OrderedManager man, T value) {
val a = unordered.put(man.id, value);
val b = ordered.put(man, value);
if (a != b) {
throw new AssertionError();
}
}
public int size() {
return unordered.size();
}
public T get(OrderedManager ord) {
return unordered.get(ord.id);
}
public T get(String id) {
return unordered.get(id);
}
public T remove(OrderedManager ord) {
val a = unordered.remove(ord.id);
val b = ordered.remove(ord);
if (a != b) {
throw new AssertionError();
}
return b;
}
public boolean containsKey(String id) {
return unordered.containsKey(id);
}
public boolean containsKey(OrderedManager ord) {
return unordered.containsKey(ord.id);
}
public Set<OrderedManager> keySet() {
return ordered.keySet();
}
public Set<Map.Entry<OrderedManager, T>> entrySet() {
return ordered.entrySet();
}
public Iterable<T> values() {
return ordered.values();
}
}
@@ -23,13 +23,19 @@
package com.falsepattern.chunk.internal.core;
import com.falsepattern.chunk.internal.Tags;
import com.falsepattern.chunk.internal.mixin.plugin.Mixin;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@IFMLLoadingPlugin.TransformerExclusions(Tags.ROOT_PKG + ".internal.core")
public class CoreLoadingPlugin implements IFMLLoadingPlugin {
public class CoreLoadingPlugin implements IFMLLoadingPlugin, IEarlyMixinLoader {
@Override
public String[] getASMTransformerClass() {
return new String[0];
@@ -54,4 +60,14 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin {
public String getAccessTransformerClass() {
return null;
}
@Override
public String getMixinConfig() {
return "mixins.chunkapi.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
return IMixins.getEarlyMixins(Mixin.class, loadedCoreMods);
}
}
@@ -20,7 +20,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla;
package com.falsepattern.chunk.internal.mixin.mixins.common.base;
import com.falsepattern.chunk.internal.DataRegistryImpl;
import lombok.val;
@@ -20,7 +20,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla;
package com.falsepattern.chunk.internal.mixin.mixins.common.base;
import com.falsepattern.chunk.internal.impl.CustomPacketMultiBlockChange;
import lombok.val;
@@ -20,7 +20,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla;
package com.falsepattern.chunk.internal.mixin.mixins.common.base;
import com.falsepattern.chunk.internal.DataRegistryImpl;
import com.falsepattern.chunk.internal.mixin.helpers.LockHelper;
@@ -20,7 +20,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla;
package com.falsepattern.chunk.internal.mixin.mixins.common.base;
import com.falsepattern.chunk.internal.DataRegistryImpl;
import com.falsepattern.chunk.internal.impl.CustomPacketBlockChange;
@@ -20,7 +20,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.vanilla;
package com.falsepattern.chunk.internal.mixin.mixins.common.base;
import com.falsepattern.chunk.internal.BlockPosUtil;
import com.falsepattern.chunk.internal.DataRegistryImpl;
@@ -0,0 +1,155 @@
/*
* ChunkAPI
*
* Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors
* Copyright (C) 2025 Cardinalstar16
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.mixins.common.thermos;
import com.falsepattern.chunk.internal.mixin.helpers.LockHelper;
import lombok.val;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.S21PacketChunkData;
import net.minecraft.network.play.server.S26PacketMapChunkBulk;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
@Mixin(S26PacketMapChunkBulk.class)
public abstract class S26PacketMapChunkBulkMixin {
@Shadow(aliases = "field_149268_i",
remap = false)
private byte[] inflaterBuffer;
@Shadow(aliases = "field_149263_e",
remap = false)
private byte[] deflatedData;
@Shadow(aliases = "field_149266_a",
remap = false)
private int[] xPositions;
@Shadow(aliases = "field_149264_b",
remap = false)
private int[] zPositions;
@Shadow(aliases = "field_149265_c",
remap = false)
private int[] subChunkMasks;
@Shadow(aliases = "field_149262_d",
remap = false)
private int[] subChunkMSBMasks;
@Shadow(aliases = "field_149261_g",
remap = false)
private int deflatedSize;
@Shadow(aliases = "field_149267_h",
remap = false)
private boolean skylight;
@Shadow(aliases = "field_149260_f",
remap = false)
private byte[][] datas;
@Shadow(remap = false)
protected abstract void compress();
/**
* @author FalsePattern
* @reason Replace functionality
*/
@Overwrite
public void readPacketData(PacketBuffer data) throws IOException {
short chunkCount = data.readShort();
val sizes = new int[chunkCount];
for (int i = 0; i < chunkCount; i++) {
sizes[i] = data.readInt();
}
deflatedSize = data.readInt();
skylight = data.readBoolean();
xPositions = new int[chunkCount];
zPositions = new int[chunkCount];
subChunkMasks = new int[chunkCount];
subChunkMSBMasks = new int[chunkCount];
datas = new byte[chunkCount][];
while (!LockHelper.bufferLockS26PacketMapChunkBulk.tryLock()) {
Thread.yield();
}
byte[] buf;
try {
if (inflaterBuffer.length < deflatedSize) {
inflaterBuffer = new byte[deflatedSize];
}
data.readBytes(inflaterBuffer, 0, deflatedSize);
buf = new byte[S21PacketChunkData.func_149275_c() * chunkCount];
Inflater inflater = new Inflater();
inflater.setInput(inflaterBuffer, 0, deflatedSize);
try {
inflater.inflate(buf);
} catch (DataFormatException dataformatexception) {
throw new IOException("Bad compressed data format");
} finally {
inflater.end();
}
} finally {
LockHelper.bufferLockS26PacketMapChunkBulk.unlock();
}
int pos = 0;
for (int i = 0; i < chunkCount; ++i) {
val size = sizes[i];
xPositions[i] = data.readInt();
zPositions[i] = data.readInt();
subChunkMasks[i] = data.readUnsignedShort();
datas[i] = new byte[size];
System.arraycopy(buf, pos, datas[i], 0, size);
pos += size;
}
}
/**
* @author FalsePattern
* @author Cardinalstar16
* @reason Replace functionality for thermos
*/
@Overwrite
public void writePacketData(PacketBuffer data) {
compress();
data.writeShort(xPositions.length);
for (byte[] bytes : datas) {
data.writeInt(bytes.length);
}
data.writeInt(deflatedSize);
data.writeBoolean(skylight);
data.writeBytes(deflatedData, 0, deflatedSize);
for (int i = 0; i < xPositions.length; ++i) {
data.writeInt(xPositions[i]);
data.writeInt(zPositions[i]);
data.writeShort((short) (subChunkMasks[i] & 65535));
}
}
}
@@ -129,7 +129,7 @@ public abstract class S26PacketMapChunkBulkMixin {
pos += size;
}
}
/**
* @author FalsePattern
* @reason Replace functionality
@@ -22,23 +22,23 @@
package com.falsepattern.chunk.internal.mixin.plugin;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.ITargetedMod;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
import com.gtnewhorizon.gtnhmixins.LateMixin;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import org.jetbrains.annotations.NotNull;
import java.util.function.Predicate;
import java.util.List;
import java.util.Set;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.ITargetedMod.PredicateHelpers.contains;
@LateMixin
public class LateMixins implements ILateMixinLoader {
@Override
public String getMixinConfig() {
return "mixins.chunkapi.late.json";
}
@RequiredArgsConstructor
public enum TargetedMod implements ITargetedMod {
LOOKINGGLASS("Looking Glass", false, contains("lookingglass-"))
;
@Getter
private final String modName;
@Getter
private final boolean loadInDevelopment;
@Getter
private final Predicate<String> condition;
@Override
public @NotNull List<String> getMixins(Set<String> loadedMods) {
return IMixins.getLateMixins(Mixin.class, loadedMods);
}
}
@@ -22,41 +22,111 @@
package com.falsepattern.chunk.internal.mixin.plugin;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.IMixin;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.ITargetedMod;
import com.falsepattern.chunk.internal.Tags;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.MixinHelper;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.SidedMixins;
import com.falsepattern.chunk.internal.mixin.plugin.fplib.TaggedMod;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizon.gtnhmixins.builders.MixinBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.intellij.lang.annotations.Language;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.BooleanSupplier;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.IMixin.PredicateHelpers.always;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.IMixin.PredicateHelpers.require;
import static com.falsepattern.chunk.internal.mixin.plugin.TargetMod.LookingGlass;
import static com.falsepattern.chunk.internal.mixin.plugin.TargetMod.Spool;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.MixinHelper.avoid;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.MixinHelper.builder;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.MixinHelper.mods;
import static com.falsepattern.chunk.internal.mixin.plugin.fplib.MixinHelper.require;
@RequiredArgsConstructor
public enum Mixin implements IMixin {
public enum Mixin implements IMixins {
// @formatter:off
AnvilChunkLoaderMixin(Side.COMMON, always(), "vanilla.AnvilChunkLoaderMixin"),
PlayerInstanceMixin(Side.COMMON, always(), "vanilla.PlayerInstanceMixin"),
S21PacketChunkDataMixin(Side.COMMON, always(), "vanilla.S21PacketChunkDataMixin"),
S22PacketMultiBlockChangeMixin(Side.COMMON, always(), "vanilla.S22PacketMultiBlockChangeMixin"),
S23PacketBlockChangeMixin(Side.COMMON, always(), "vanilla.S23PacketBlockChangeMixin"),
S26PacketMapChunkBulkMixin(Side.COMMON, always(), "vanilla.S26PacketMapChunkBulkMixin"),
VanillaCore(Phase.EARLY,
() -> !hasThermos(),
common("vanilla.S26PacketMapChunkBulkMixin")),
ThermosCore(Phase.EARLY,
() -> hasThermos(),
common("thermos.S26PacketMapChunkBulkMixin")),
CommonCore(Phase.EARLY,
common("base.PlayerInstanceMixin",
"base.S21PacketChunkDataMixin",
"base.S22PacketMultiBlockChangeMixin",
"base.S23PacketBlockChangeMixin"),
client("vanilla.NetHandlerPlayClientMixin")),
//from: https://github.com/BallOfEnergy1/ChunkAPI/commit/4f5c0e60e04b6892d206f5f5d93cb20ba6b45608
Core_NoSpool(Phase.EARLY,
avoid(Spool),
common("base.AnvilChunkLoaderMixin"),
client("vanilla.ChunkMixin")),
ChunkMixin(Side.CLIENT, always(), "vanilla.ChunkMixin"),
NetHandlerPlayClientMixin(Side.CLIENT, always(), "vanilla.NetHandlerPlayClientMixin"),
//region Looking Glass
PacketChunkInfoMixin(Side.COMMON, require(TargetedMod.LOOKINGGLASS), "lookingglass.PacketChunkInfoMixin"),
//endregion
;
Compat_LookingGlass(Phase.LATE,
require(LookingGlass),
common("lookingglass.PacketChunkInfoMixin")),
// @formatter:on
//region boilerplate
;
@Getter
private final Side side;
@Getter
private final Predicate<List<ITargetedMod>> filter;
@Getter
private final String mixin;
}
private final MixinBuilder builder;
Mixin(Phase phase, SidedMixins... mixins) {
this(builder(mixins).setPhase(phase));
}
Mixin(Phase phase, BooleanSupplier cond, SidedMixins... mixins) {
this(builder(cond, mixins).setPhase(phase));
}
Mixin(Phase phase, TaggedMod mod, SidedMixins... mixins) {
this(builder(mod, mixins).setPhase(phase));
}
Mixin(Phase phase, TaggedMod[] mods, SidedMixins... mixins) {
this(builder(mods, mixins).setPhase(phase));
}
Mixin(Phase phase, BooleanSupplier cond, TaggedMod mod, SidedMixins... mixins) {
this(builder(cond, mod, mixins).setPhase(phase));
}
Mixin(Phase phase, BooleanSupplier cond, TaggedMod[] mods, SidedMixins... mixins) {
this(builder(cond, mods, mixins).setPhase(phase));
}
private static SidedMixins common(@Language(value = "JAVA",
prefix = "import " + Tags.ROOT_PKG + ".internal.mixin.mixins.common.",
suffix = ";") String... mixins) {
return MixinHelper.common(mixins);
}
private static SidedMixins client(@Language(value = "JAVA",
prefix = "import " + Tags.ROOT_PKG + ".internal.mixin.mixins.client.",
suffix = ";") String... mixins) {
return MixinHelper.client(mixins);
}
private static SidedMixins server(@Language(value = "JAVA",
prefix = "import " + Tags.ROOT_PKG + ".mixin.mixins.server.",
suffix = ";") String... mixins) {
return MixinHelper.server(mixins);
}
//endregion
static boolean hasThermos()
{
try
{
Class.forName("thermos.Thermos");
return true;
}
catch(ClassNotFoundException ignored) {
return false;
}
}
}
@@ -0,0 +1,56 @@
/*
* ChunkAPI
*
* Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin;
import com.gtnewhorizon.gtnhmixins.builders.ITargetMod;
import com.gtnewhorizon.gtnhmixins.builders.TargetModBuilder;
import lombok.Getter;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
public enum TargetMod implements ITargetMod {
LookingGlass("com.xcompwiz.lookingglass.LookingGlass"),
Spool("com.gamma.spool.core.Spool"),
;
@Getter
private final TargetModBuilder builder;
TargetMod(@Language(value = "JAVA",
prefix = "import ",
suffix = ";") @NotNull String className) {
this(className, null);
}
TargetMod(@Language(value = "JAVA",
prefix = "import ",
suffix = ";") @NotNull String className, @Nullable Consumer<TargetModBuilder> cfg) {
builder = new TargetModBuilder();
builder.setTargetClass(className);
if (cfg != null) {
cfg.accept(builder);
}
}
}
@@ -1,77 +0,0 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import lombok.val;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface IMixin {
String getMixin();
Side getSide();
Predicate<List<ITargetedMod>> getFilter();
default boolean shouldLoad(List<ITargetedMod> loadedMods) {
val side = getSide();
return (side == Side.COMMON
|| side == Side.SERVER && FMLLaunchHandler.side().isServer()
|| side == Side.CLIENT && FMLLaunchHandler.side().isClient()) && getFilter().test(loadedMods);
}
/**
* @since 0.10.0
*/
enum Side {
COMMON,
CLIENT,
SERVER
}
/**
* @since 0.10.0
*/
final class PredicateHelpers {
public static Predicate<List<ITargetedMod>> never() {
return (list) -> false;
}
public static Predicate<List<ITargetedMod>> condition(Supplier<Boolean> condition) {
return (list) -> condition.get();
}
public static Predicate<List<ITargetedMod>> always() {
return (list) -> true;
}
public static Predicate<List<ITargetedMod>> require(ITargetedMod mod) {
return (list) -> list.contains(mod);
}
public static Predicate<List<ITargetedMod>> avoid(ITargetedMod mod) {
return (list) -> !list.contains(mod);
}
}
}
@@ -1,218 +0,0 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import lombok.val;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import net.minecraft.launchwrapper.Launch;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.nio.file.Files.walk;
public interface IMixinPlugin extends IMixinConfigPlugin {
Path MODS_DIRECTORY_PATH = FileUtil.getMinecraftHome().toPath().resolve("mods");
static Logger createLogger(String modName) {
return LogManager.getLogger(modName + " Mixin Loader");
}
/**
* @deprecated since 1.4.0
*/
@Deprecated
static File findJarOf(final ITargetedMod mod) {
File result = null;
try (val stream = walk(MODS_DIRECTORY_PATH)) {
result = stream.filter(mod::isMatchingJar).map(Path::toFile).findFirst().orElse(null);
} catch (Exception e) {
e.printStackTrace();
}
if (result == null) {
File found = null;
for (URL url : Launch.classLoader.getURLs()) {
try {
val file = new File(url.toURI());
Path path = file.toPath();
if (mod.isMatchingJar(path)) {
found = file;
break;
}
} catch (Exception ignored) {
}
}
result = found;
}
return result;
}
/**
* @since 1.4.0
*/
static Set<File> findJarsOf(IMixinPlugin self, final ITargetedMod mod) {
if (!self.useNewFindJar()) {
val jar = findJarOf(mod);
return jar == null ? Collections.emptySet() : Collections.singleton(jar);
}
val results = new HashSet<File>();
try (val stream = walk(MODS_DIRECTORY_PATH)) {
results.addAll(stream.filter(mod::isMatchingJar).map(Path::toFile).collect(Collectors.toSet()));
} catch (Exception e) {
e.printStackTrace();
}
for (URL url : Launch.classLoader.getURLs()) {
try {
val file = new File(url.toURI());
Path path = file.toPath();
if (mod.isMatchingJar(path)) {
results.add(file);
break;
}
} catch (Exception ignored) {
}
}
return results;
}
Logger getLogger();
IMixin[] getMixinEnumValues();
ITargetedMod[] getTargetedModEnumValues();
/**
* @since 1.4.0
*/
default boolean useNewFindJar() {
return false;
}
@Override
@ApiStatus.Internal
default void onLoad(String mixinPackage) {
}
@Override
@ApiStatus.Internal
default String getRefMapperConfig() {
return null;
}
@Override
@ApiStatus.Internal
default boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}
@Override
@ApiStatus.Internal
default void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
@ApiStatus.Internal
default List<String> getMixins() {
val isDevelopmentEnvironment = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
val targetedMods = getTargetedModEnumValues();
val loadedMods = Arrays.stream(targetedMods)
//Java 21 compat
.filter(new Predicate<ITargetedMod>() {
@Override
public boolean test(ITargetedMod mod) {
boolean loadJar = IMixinPlugin.this.loadJarOf(mod);
return (mod.isLoadInDevelopment() && isDevelopmentEnvironment)
|| loadJar;
}
})
.collect(Collectors.toList());
for (val mod : targetedMods) {
if (loadedMods.contains(mod)) {
getLogger().info("Found " + mod.getModName() + "! Integrating now...");
} else {
getLogger().info("Could not find " + mod.getModName() + "! Skipping integration....");
}
}
List<String> mixins = new ArrayList<>();
for (val mixin : getMixinEnumValues()) {
if (mixin.shouldLoad(loadedMods)) {
String mixinClass = mixin.getSide().name().toLowerCase() + "." + mixin.getMixin();
mixins.add(mixinClass);
getLogger().info("Loading mixin: " + mixinClass);
}
}
return mixins;
}
@ApiStatus.Internal
default boolean loadJarOf(final ITargetedMod mod) {
try {
val jars = findJarsOf(this, mod);
if (jars.isEmpty()) {
getLogger().info("Jar not found for {}", mod);
return false;
}
for (val jar: jars) {
getLogger().info("Attempting to add {} to the URL Class Path", jar);
try {
MinecraftURLClassPath.addJar(jar);
} catch (Throwable e) {
e.printStackTrace();
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return true;
}
@Override
@ApiStatus.Internal
default void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
@ApiStatus.Internal
default void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}
@@ -1,59 +0,0 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import com.google.common.io.Files;
import java.nio.file.Path;
import java.util.function.Predicate;
public interface ITargetedMod {
String getModName();
boolean isLoadInDevelopment();
@SuppressWarnings("UnstableApiUsage")
default boolean isMatchingJar(Path path) {
String pathString = path.toString();
String nameLowerCase = Files.getNameWithoutExtension(pathString).toLowerCase();
String fileExtension = Files.getFileExtension(pathString);
return "jar".equals(fileExtension) && getCondition().test(nameLowerCase);
}
Predicate<String> getCondition();
final class PredicateHelpers {
public static Predicate<String> startsWith(String subString) {
return (name) -> name.startsWith(subString.toLowerCase());
}
public static Predicate<String> contains(String subString) {
return (name) -> name.contains(subString.toLowerCase());
}
public static Predicate<String> matches(String regex) {
return (name) -> name.matches(regex);
}
}
}
@@ -1,19 +0,0 @@
FalsePatternLib
Copyright (C) 2022-2025 FalsePattern
All Rights Reserved
The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, only version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -0,0 +1,112 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import com.gtnewhorizon.gtnhmixins.builders.IBaseTransformer;
import com.gtnewhorizon.gtnhmixins.builders.ITargetMod;
import com.gtnewhorizon.gtnhmixins.builders.MixinBuilder;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BooleanSupplier;
public class MixinHelper {
public static @NotNull MixinBuilder builder(@NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(null, null, mixins);
}
public static @NotNull MixinBuilder builder(@Nullable BooleanSupplier cond, @NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(cond, null, mixins);
}
public static @NotNull MixinBuilder builder(@NotNull TaggedMod mod, @NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(null, mods(mod), mixins);
}
public static @NotNull MixinBuilder builder(@NotNull TaggedMod @Nullable [] mods, @NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(null, mods, mixins);
}
public static @NotNull MixinBuilder builder(@NotNull BooleanSupplier cond, @NotNull TaggedMod mod, @NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(cond, mods(mod), mixins);
}
public static @NotNull MixinBuilder builder(@NotNull BooleanSupplier cond, @NotNull TaggedMod @NotNull [] mods, @NotNull SidedMixins @NotNull ... mixins) {
return builderImpl(cond, mods, mixins);
}
private static @NotNull MixinBuilder builderImpl(@Nullable BooleanSupplier cond, @NotNull TaggedMod @Nullable [] requiredMods, @NotNull SidedMixins @NotNull ... mixins) {
val builder = new MixinBuilder();
if (requiredMods != null) {
for (val mod : requiredMods) {
if (mod.require) {
builder.addRequiredMod(mod.mod);
} else {
builder.addExcludedMod(mod.mod);
}
}
}
for (val mixin : mixins) {
builder.addSidedMixins(mixin.side, mixin.mixins);
}
if (cond != null) {
builder.setApplyIf(cond::getAsBoolean);
}
return builder;
}
public static @NotNull SidedMixins common(@NotNull String @NotNull ... mixins) {
for (int i = 0; i < mixins.length; i++) {
mixins[i] = "common." + mixins[i];
}
return new SidedMixins(IBaseTransformer.Side.COMMON, mixins);
}
public static @NotNull SidedMixins client(@NotNull String @NotNull ... mixins) {
for (int i = 0; i < mixins.length; i++) {
mixins[i] = "client." + mixins[i];
}
return new SidedMixins(IBaseTransformer.Side.CLIENT, mixins);
}
public static @NotNull SidedMixins server(@NotNull String @NotNull ... mixins) {
for (int i = 0; i < mixins.length; i++) {
mixins[i] = "server." + mixins[i];
}
return new SidedMixins(IBaseTransformer.Side.SERVER, mixins);
}
public static @NotNull TaggedMod require(@NotNull ITargetMod mod) {
return new TaggedMod(true, mod);
}
public static @NotNull TaggedMod avoid(@NotNull ITargetMod mod) {
return new TaggedMod(false, mod);
}
public static @NotNull TaggedMod @NotNull [] mods(@NotNull TaggedMod @NotNull ... t) {
return t;
}
}
@@ -1 +0,0 @@
Shadowed from FalsePatternLib 1.7.0
@@ -19,23 +19,15 @@
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import com.gtnewhorizon.gtnhmixins.builders.IBaseTransformer;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Backport from spongemixins 1.3 for compat with the curseforge 1.2.0 version.
* <p>
* Also added Grimoire protection.
*
* @since 0.10.0
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MinecraftURLClassPath {
public static void addJar(File pathToJar) throws Exception {
UCPImpl.addJar(pathToJar);
}
@RequiredArgsConstructor
public class SidedMixins {
public final IBaseTransformer.@NotNull Side side;
public final @NotNull String @NotNull [] mixins;
}
@@ -19,29 +19,15 @@
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import lombok.experimental.UtilityClass;
import com.gtnewhorizon.gtnhmixins.builders.ITargetMod;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import net.minecraft.launchwrapper.Launch;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @since 0.8.2
*/
@UtilityClass
public final class FileUtil {
public static File getMinecraftHome() {
return getMinecraftHomePath().toFile();
}
/**
* @since 1.0.0
*/
public static Path getMinecraftHomePath() {
return Launch.minecraftHome == null ? Paths.get("").toAbsolutePath() : Launch.minecraftHome.toPath();
}
}
@RequiredArgsConstructor
public class TaggedMod {
public final boolean require;
public final @NotNull ITargetMod mod;
}
@@ -0,0 +1,57 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import com.gtnewhorizon.gtnhmixins.builders.ITargetMod;
import com.gtnewhorizon.gtnhmixins.builders.TargetModBuilder;
import lombok.Getter;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
public class TargetMod implements ITargetMod {
@Getter
private final TargetModBuilder builder;
private final String name;
public TargetMod(@NotNull String name, @Language(value = "JAVA", prefix = "import ", suffix = ";") @NotNull String className) {
this(name, className, null);
}
public TargetMod(@NotNull String name, @Language(value = "JAVA", prefix = "import ", suffix = ";") @NotNull String className, @Nullable Consumer<TargetModBuilder> cfg) {
this.name = name;
builder = new TargetModBuilder();
builder.setTargetClass(className);
if (cfg != null) {
cfg.accept(builder);
}
}
@Override
public String toString() {
return name;
}
}
@@ -1,61 +0,0 @@
/*
* This file is part of FalsePatternLib.
*
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.chunk.internal.mixin.plugin.fplib;
import lombok.val;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import java.io.File;
import java.io.IOException;
public final class UCPImpl {
private static final boolean GRIMOIRE;
static {
boolean grimoire = false;
String[] knownGrimoireClassNames =
new String[]{"io.github.crucible.grimoire.Grimoire", "io.github.crucible.grimoire.common.GrimoireCore"};
for (val className : knownGrimoireClassNames) {
try {
if (Launch.classLoader.getClassBytes(className) != null) {
grimoire = true;
break;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
GRIMOIRE = grimoire;
}
public static void addJar(File pathToJar) throws Exception {
if (!GRIMOIRE) {
final LaunchClassLoader loader = Launch.classLoader;
loader.addURL(pathToJar.toURI().toURL());
// Act as-if we only added the mod to ucp
loader.getSources().remove(loader.getSources().size() - 1);
}
}
}
@@ -1,23 +1,23 @@
/*
* ChunkAPI
* This file is part of FalsePatternLib.
*
* Copyright (C) 2023-2025 FalsePattern, The MEGA Team, LegacyModdingMC contributors
* Copyright (C) 2022-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* FalsePatternLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* FalsePatternLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
*/
@ApiStatus.Internal
@@ -40,7 +40,7 @@ import java.nio.ByteBuffer;
import static com.falsepattern.chunk.internal.Common.BLOCKS_PER_SUBCHUNK;
import static com.falsepattern.chunk.internal.Common.SUBCHUNKS_PER_CHUNK;
public class BlockIDManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.BlockPacketDataManager, DataManager.SubChunkDataManager {
public class BlockIDManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.CubicPacketDataManager, DataManager.BlockPacketDataManager, DataManager.SubChunkDataManager {
private static final int LSB_BYTES_PER_SUBCHUNK = BLOCKS_PER_SUBCHUNK;
private static final int MSB_BYTES_PER_SUBCHUNK = BLOCKS_PER_SUBCHUNK / 2;
private static final int HEADER_SIZE = 2;
@@ -100,6 +100,36 @@ public class BlockIDManager extends VanillaManager implements DataManager.Packet
}
}
@Override
public int maxPacketSizeCubic() {
return HEADER_SIZE + LSB_BYTES_PER_SUBCHUNK + MSB_BYTES_PER_SUBCHUNK;
}
@Override
public void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) {
buffer.put(blockStorage.getBlockLSBArray());
if (blockStorage.getBlockMSBArray() != null) {
buffer.put((byte) 1);
buffer.put(blockStorage.getBlockMSBArray().data);
} else {
buffer.put((byte) 0);
}
}
@Override
public void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) {
buffer.get(blockStorage.getBlockLSBArray());
if (buffer.get() != 0) {
if (blockStorage.getBlockMSBArray() == null) {
blockStorage.createBlockMSBArray();
}
buffer.get(blockStorage.getBlockMSBArray().data);
}
}
@Override
public boolean subChunkPrivilegedAccess() {
return true;
@@ -32,7 +32,7 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import java.nio.ByteBuffer;
public abstract class NibbleManager extends VanillaManager implements DataManager.PacketDataManager {
public abstract class NibbleManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.CubicPacketDataManager {
public static final int BYTES_PER_SUBCHUNK = Common.BLOCKS_PER_SUBCHUNK / 2;
protected abstract NibbleArray getNibbleArray(ExtendedBlockStorage subChunk);
@@ -67,4 +67,19 @@ public abstract class NibbleManager extends VanillaManager implements DataManage
}
}
}
@Override
public int maxPacketSizeCubic() {
return BYTES_PER_SUBCHUNK;
}
@Override
public void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) {
buffer.put(getNibbleArray(blockStorage).data, 0, BYTES_PER_SUBCHUNK);
}
@Override
public void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) {
buffer.get(getNibbleArray(blockStorage).data, 0, BYTES_PER_SUBCHUNK);
}
}
@@ -2,7 +2,6 @@
"required": true,
"minVersion": "0.8.5",
"package": "com.falsepattern.chunk.internal.mixin.mixins",
"plugin": "com.falsepattern.chunk.internal.mixin.plugin.MixinPlugin",
"refmap": "mixins.chunkapi.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8"
@@ -0,0 +1,9 @@
{
"required": true,
"minVersion": "0.8.5",
"package": "com.falsepattern.chunk.internal.mixin.mixins",
"refmap": "mixins.chunkapi.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8"
}