diff --git a/build.gradle b/build.gradle index 0ec26eb4..0b402b79 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,14 @@ subprojects { repositories { maven { url "https://maven.neoforged.net/releases/" } + if (rootProject.neoforge_pr != "") { + maven { + url "https://prmaven.neoforged.net/NeoForge/pr$rootProject.neoforge_pr" + content { + includeModule("net.neoforged", "neoforge") + } + } + } } } diff --git a/common/src/main/java/dev/architectury/event/events/common/LootEvent.java b/common/src/main/java/dev/architectury/event/events/common/LootEvent.java index 6ceb78db..65a09cef 100644 --- a/common/src/main/java/dev/architectury/event/events/common/LootEvent.java +++ b/common/src/main/java/dev/architectury/event/events/common/LootEvent.java @@ -46,9 +46,9 @@ public interface LootEvent { * *
{@code
- * LootEvent.MODIFY_LOOT_TABLE.register((lootTables, id, context, builtin) -> {
+ * LootEvent.MODIFY_LOOT_TABLE.register((key, context, builtin) -> {
* // Check that the loot table is dirt and built-in
- * if (builtin && Blocks.DIRT.getLootTable().equals(id)) {
+ * if (builtin && Blocks.DIRT.getLootTable().equals(key)) {
* // Create a loot pool with a single item entry of Items.DIAMOND
* LootPool.Builder pool = LootPool.lootPool().add(LootItem.lootTableItem(Items.DIAMOND));
* context.addPool(pool);
@@ -58,7 +58,7 @@ public interface LootEvent {
*
* @see ModifyLootTable#modifyLootTable(ResourceKey, LootTableModificationContext, boolean)
*/
- // Event MODIFY_LOOT_TABLE = EventFactory.createLoop();
+ Event MODIFY_LOOT_TABLE = EventFactory.createLoop();
@FunctionalInterface
interface ModifyLootTable {
diff --git a/common/src/main/java/dev/architectury/fluid/FluidStack.java b/common/src/main/java/dev/architectury/fluid/FluidStack.java
index f30ef281..6d0d8d34 100644
--- a/common/src/main/java/dev/architectury/fluid/FluidStack.java
+++ b/common/src/main/java/dev/architectury/fluid/FluidStack.java
@@ -19,7 +19,6 @@
package dev.architectury.fluid;
-import com.google.common.collect.Iterators;
import com.mojang.serialization.Codec;
import dev.architectury.hooks.fluid.FluidStackHooks;
import dev.architectury.injectables.annotations.ExpectPlatform;
@@ -35,7 +34,8 @@ import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
+import java.util.Objects;
+import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -66,100 +66,6 @@ public final class FluidStack implements DataComponentHolder {
throw new AssertionError();
}
- @Override
- public DataComponentMap getComponents() {
- return new DataComponentMap() {
- @Nullable
- @Override
- public T get(DataComponentType extends T> type) {
- return getPatch().get(type).orElse(null);
- }
-
- @Override
- public Set> keySet() {
- return new AbstractSet<>() {
- @Override
- public Iterator> iterator() {
- return Iterators.transform(getPatch().entrySet().iterator(), Map.Entry::getKey);
- }
-
- @Override
- public int size() {
- return getPatch().entrySet().size();
- }
-
- @Override
- public boolean contains(Object o) {
- if (!(o instanceof DataComponentType> type)) return false;
- return getPatch().get(type).isPresent();
- }
- };
- }
- };
- }
-
- public T set(DataComponentType super T> dataComponentType, @Nullable T object) {
- T previous = (T) get(dataComponentType);
- DataComponentPatch.Builder builder = DataComponentPatch.builder();
- for (TypedDataComponent> component : getComponents()) {
- if (component.type() != dataComponentType) {
- builder.set(component);
- }
- }
- if (object != null) {
- builder.set(dataComponentType, object);
- }
- setPatch(builder.build());
- return previous;
- }
-
- @Nullable
- public T update(DataComponentType dataComponentType, T object, U object2, BiFunction biFunction) {
- return this.set(dataComponentType, biFunction.apply(this.getOrDefault(dataComponentType, object), object2));
- }
-
- @Nullable
- public T update(DataComponentType dataComponentType, T object, UnaryOperator unaryOperator) {
- return this.set(dataComponentType, unaryOperator.apply(this.getOrDefault(dataComponentType, object)));
- }
-
- @Nullable
- public T remove(DataComponentType extends T> dataComponentType) {
- return this.set(dataComponentType, null);
- }
-
- public void applyComponents(DataComponentPatch dataComponentPatch) {
- DataComponentPatch.Builder builder = DataComponentPatch.builder();
- for (TypedDataComponent> component : getComponents()) {
- builder.set(component);
- }
- for (Map.Entry, Optional>> entry : dataComponentPatch.entrySet()) {
- if (entry.getValue().isPresent()) {
- //noinspection rawtypes
- builder.set((DataComponentType) entry.getKey(), entry.getValue().get());
- } else {
- builder.remove(entry.getKey());
- }
- }
- setPatch(builder.build());
- }
-
- public void applyComponents(DataComponentMap dataComponentMap) {
- DataComponentPatch.Builder builder = DataComponentPatch.builder();
- for (TypedDataComponent> component : getComponents()) {
- builder.set(component);
- }
- for (TypedDataComponent> entry : dataComponentMap) {
- if (entry.value() != null) {
- //noinspection rawtypes
- builder.set((DataComponentType) entry.type(), entry.value());
- } else {
- builder.remove(entry.type());
- }
- }
- setPatch(builder.build());
- }
-
@ApiStatus.Internal
public interface FluidStackAdapter {
T create(Supplier fluid, long amount, @Nullable DataComponentPatch patch);
@@ -174,7 +80,19 @@ public final class FluidStack implements DataComponentHolder {
DataComponentPatch getPatch(T value);
- void setPatch(T value, DataComponentPatch patch);
+ PatchedDataComponentMap getComponents(T value);
+
+ void applyComponents(T value, DataComponentPatch patch);
+
+ void applyComponents(T value, DataComponentMap patch);
+
+ @Nullable D set(T value, DataComponentType super D> type, @Nullable D component);
+
+ @Nullable D remove(T value, DataComponentType extends D> type);
+
+ @Nullable D update(T value, DataComponentType type, D component, UnaryOperator updater);
+
+ @Nullable D update(T value, DataComponentType type, D component, U updateContext, BiFunction updater);
T copy(T value);
@@ -260,8 +178,37 @@ public final class FluidStack implements DataComponentHolder {
return ADAPTER.getPatch(value);
}
- public void setPatch(DataComponentPatch patch) {
- ADAPTER.setPatch(value, patch);
+ @Override
+ public PatchedDataComponentMap getComponents() {
+ return ADAPTER.getComponents(value);
+ }
+
+ public void applyComponents(DataComponentPatch patch) {
+ ADAPTER.applyComponents(value, patch);
+ }
+
+ public void applyComponents(DataComponentMap patch) {
+ ADAPTER.applyComponents(value, patch);
+ }
+
+ @Nullable
+ public T set(DataComponentType super T> type, @Nullable T component) {
+ return ADAPTER.set(value, type, component);
+ }
+
+ @Nullable
+ public T remove(DataComponentType extends T> type) {
+ return ADAPTER.remove(value, type);
+ }
+
+ @Nullable
+ public T update(DataComponentType type, T component, UnaryOperator updater) {
+ return ADAPTER.update(value, type, component, updater);
+ }
+
+ @Nullable
+ public T update(DataComponentType type, T component, U updateContext, BiFunction updater) {
+ return ADAPTER.update(value, type, component, updateContext, updater);
}
public Component getName() {
diff --git a/common/src/main/java/dev/architectury/impl/NetworkAggregator.java b/common/src/main/java/dev/architectury/impl/NetworkAggregator.java
new file mode 100644
index 00000000..2d3e67ed
--- /dev/null
+++ b/common/src/main/java/dev/architectury/impl/NetworkAggregator.java
@@ -0,0 +1,215 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.impl;
+
+import com.google.common.base.Suppliers;
+import dev.architectury.networking.NetworkManager;
+import dev.architectury.networking.transformers.PacketSink;
+import dev.architectury.networking.transformers.PacketTransformer;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.ByteBufCodecs;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+@ApiStatus.Internal
+public class NetworkAggregator {
+ public static final Supplier ADAPTOR = Suppliers.memoize(() -> {
+ try {
+ Method adaptor = NetworkManager.class.getDeclaredMethod("getAdaptor");
+ adaptor.setAccessible(true);
+ return (Adaptor) adaptor.invoke(null);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ });
+ public static final Map> C2S_TYPE = new HashMap<>();
+ public static final Map> S2C_TYPE = new HashMap<>();
+ public static final Map> C2S_RECEIVER = new HashMap<>();
+ public static final Map> S2C_RECEIVER = new HashMap<>();
+ public static final Map> C2S_CODECS = new HashMap<>();
+ public static final Map> S2C_CODECS = new HashMap<>();
+ public static final Map C2S_TRANSFORMERS = new HashMap<>();
+ public static final Map S2C_TRANSFORMERS = new HashMap<>();
+
+ public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, List packetTransformers, NetworkManager.NetworkReceiver receiver) {
+ CustomPacketPayload.Type type = new CustomPacketPayload.Type<>(id);
+ if (side == NetworkManager.Side.C2S) {
+ C2S_TYPE.put(id, type);
+ registerC2SReceiver(type, BufCustomPacketPayload.streamCodec(type), packetTransformers, (value, context) -> {
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(value.payload()), context.registryAccess());
+ receiver.receive(buf, context);
+ buf.release();
+ });
+ } else if (side == NetworkManager.Side.S2C) {
+ S2C_TYPE.put(id, type);
+ registerS2CReceiver(type, BufCustomPacketPayload.streamCodec(type), packetTransformers, (value, context) -> {
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(value.payload()), context.registryAccess());
+ receiver.receive(buf, context);
+ buf.release();
+ });
+ }
+ }
+
+ public static void registerReceiver(NetworkManager.Side side, CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers, NetworkManager.NetworkReceiver receiver) {
+ Objects.requireNonNull(type, "Cannot register receiver with a null type!");
+ packetTransformers = Objects.requireNonNullElse(packetTransformers, List.of());
+ Objects.requireNonNull(receiver, "Cannot register a null receiver!");
+ if (side == NetworkManager.Side.C2S) {
+ registerC2SReceiver(type, codec, packetTransformers, receiver);
+ } else if (side == NetworkManager.Side.S2C) {
+ registerS2CReceiver(type, codec, packetTransformers, receiver);
+ }
+ }
+
+ private static void registerC2SReceiver(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers, NetworkManager.NetworkReceiver receiver) {
+ PacketTransformer transformer = PacketTransformer.concat(packetTransformers);
+ C2S_RECEIVER.put(type.id(), receiver);
+ C2S_CODECS.put(type.id(), (StreamCodec) codec);
+ C2S_TRANSFORMERS.put(type.id(), transformer);
+ ADAPTOR.get().registerC2S((CustomPacketPayload.Type) type, BufCustomPacketPayload.streamCodec((CustomPacketPayload.Type) type), (payload, context) -> {
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(payload.payload()), context.registryAccess());
+ transformer.inbound(NetworkManager.Side.C2S, type.id(), buf, context, (side, id1, buf1) -> {
+ NetworkManager.NetworkReceiver networkReceiver = (NetworkManager.NetworkReceiver) (side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1));
+ if (networkReceiver == null) {
+ throw new IllegalArgumentException("Network Receiver not found! " + id1);
+ }
+ T actualPayload = codec.decode(buf1);
+ networkReceiver.receive(actualPayload, context);
+ });
+ buf.release();
+ });
+ }
+
+ private static void registerS2CReceiver(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers, NetworkManager.NetworkReceiver receiver) {
+ PacketTransformer transformer = PacketTransformer.concat(packetTransformers);
+ S2C_RECEIVER.put(type.id(), receiver);
+ S2C_CODECS.put(type.id(), (StreamCodec) codec);
+ S2C_TRANSFORMERS.put(type.id(), transformer);
+ ADAPTOR.get().registerS2C((CustomPacketPayload.Type) type, BufCustomPacketPayload.streamCodec((CustomPacketPayload.Type) type), (payload, context) -> {
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(payload.payload()), context.registryAccess());
+ transformer.inbound(NetworkManager.Side.S2C, type.id(), buf, context, (side, id1, buf1) -> {
+ NetworkManager.NetworkReceiver networkReceiver = (NetworkManager.NetworkReceiver) (side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1));
+ if (networkReceiver == null) {
+ throw new IllegalArgumentException("Network Receiver not found! " + id1);
+ }
+ T actualPayload = codec.decode(buf1);
+ networkReceiver.receive(actualPayload, context);
+ });
+ buf.release();
+ });
+ }
+
+ public static void collectPackets(PacketSink sink, NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf) {
+ if (side == NetworkManager.Side.C2S) {
+ collectPackets(sink, side, new BufCustomPacketPayload(C2S_TYPE.get(id), ByteBufUtil.getBytes(buf)), buf.registryAccess());
+ } else {
+ collectPackets(sink, side, new BufCustomPacketPayload(S2C_TYPE.get(id), ByteBufUtil.getBytes(buf)), buf.registryAccess());
+ }
+ }
+
+ public static void collectPackets(PacketSink sink, NetworkManager.Side side, T payload, RegistryAccess access) {
+ CustomPacketPayload.Type type = (CustomPacketPayload.Type) payload.type();
+ PacketTransformer transformer = side == NetworkManager.Side.C2S ? C2S_TRANSFORMERS.get(type.id()) : S2C_TRANSFORMERS.get(type.id());
+ StreamCodec codec = (StreamCodec) (side == NetworkManager.Side.C2S ? C2S_CODECS.get(type.id()) : S2C_CODECS.get(type.id()));
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), access);
+ codec.encode(buf, payload);
+
+ if (transformer != null) {
+ transformer.outbound(side, type.id(), buf, (side1, id1, buf1) -> {
+ if (side == NetworkManager.Side.C2S) {
+ CustomPacketPayload.Type type1 = C2S_TYPE.getOrDefault(id1, (CustomPacketPayload.Type) type);
+ sink.accept(toPacket(side1, new BufCustomPacketPayload(type1, ByteBufUtil.getBytes(buf1))));
+ } else if (side == NetworkManager.Side.S2C) {
+ CustomPacketPayload.Type type1 = S2C_TYPE.getOrDefault(id1, (CustomPacketPayload.Type) type);
+ sink.accept(toPacket(side1, new BufCustomPacketPayload(type1, ByteBufUtil.getBytes(buf1))));
+ }
+ });
+ } else {
+ sink.accept(toPacket(side, new BufCustomPacketPayload((CustomPacketPayload.Type) type, ByteBufUtil.getBytes(buf))));
+ }
+ buf.release();
+ }
+
+ public static Packet> toPacket(NetworkManager.Side side, T payload) {
+ if (side == NetworkManager.Side.C2S) {
+ return ADAPTOR.get().toC2SPacket(payload);
+ } else if (side == NetworkManager.Side.S2C) {
+ return ADAPTOR.get().toS2CPacket(payload);
+ }
+
+ throw new IllegalArgumentException("Invalid side: " + side);
+ }
+
+ public static void registerS2CType(ResourceLocation id, List packetTransformers) {
+ CustomPacketPayload.Type type = new CustomPacketPayload.Type<>(id);
+ S2C_TYPE.put(id, type);
+ registerS2CType(type, BufCustomPacketPayload.streamCodec(type), packetTransformers);
+ }
+
+ public static void registerS2CType(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers) {
+ Objects.requireNonNull(type, "Cannot register a null type!");
+ packetTransformers = Objects.requireNonNullElse(packetTransformers, List.of());
+ S2C_CODECS.put(type.id(), (StreamCodec) codec);
+ S2C_TRANSFORMERS.put(type.id(), PacketTransformer.concat(packetTransformers));
+ ADAPTOR.get().registerS2CType((CustomPacketPayload.Type) type, BufCustomPacketPayload.streamCodec((CustomPacketPayload.Type) type));
+ }
+
+ public interface Adaptor {
+ void registerC2S(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, NetworkManager.NetworkReceiver receiver);
+
+ @Environment(EnvType.CLIENT)
+ void registerS2C(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, NetworkManager.NetworkReceiver receiver);
+
+ Packet> toC2SPacket(T payload);
+
+ @Environment(EnvType.CLIENT)
+ Packet> toS2CPacket(T payload);
+
+ void registerS2CType(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec);
+ }
+
+ public record BufCustomPacketPayload(Type _type,
+ byte[] payload) implements CustomPacketPayload {
+ @Override
+ public Type extends CustomPacketPayload> type() {
+ return this._type();
+ }
+
+ public static StreamCodec streamCodec(Type type) {
+ return ByteBufCodecs.BYTE_ARRAY.map(bytes -> new BufCustomPacketPayload(type, bytes), BufCustomPacketPayload::payload);
+ }
+ }
+}
diff --git a/common/src/main/java/dev/architectury/networking/NetworkChannel.java b/common/src/main/java/dev/architectury/networking/NetworkChannel.java
index acb37d99..c0c560b4 100644
--- a/common/src/main/java/dev/architectury/networking/NetworkChannel.java
+++ b/common/src/main/java/dev/architectury/networking/NetworkChannel.java
@@ -27,12 +27,16 @@ import io.netty.buffer.Unpooled;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -60,7 +64,7 @@ public final class NetworkChannel {
var s = UUID.nameUUIDFromBytes(type.getName().getBytes(StandardCharsets.UTF_8)).toString().replace("-", "");
var info = new MessageInfo(new ResourceLocation(id + "/" + s), encoder, decoder, messageConsumer);
encoders.put(type, info);
- NetworkManager.NetworkReceiver receiver = (buf, context) -> {
+ NetworkManager.NetworkReceiver receiver = (buf, context) -> {
info.messageConsumer.accept(info.decoder.apply(buf), () -> context);
};
NetworkManager.registerReceiver(NetworkManager.c2s(), info.packetId, receiver);
@@ -78,19 +82,21 @@ public final class NetworkChannel {
return h;
}
- public Packet> toPacket(NetworkManager.Side side, T message) {
+ public Packet> toPacket(NetworkManager.Side side, T message, RegistryAccess access) {
var messageInfo = (MessageInfo) Objects.requireNonNull(encoders.get(message.getClass()), "Unknown message type! " + message);
- var buf = new FriendlyByteBuf(Unpooled.buffer());
+ var buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), access);
messageInfo.encoder.accept(message, buf);
return NetworkManager.toPacket(side, messageInfo.packetId, buf);
}
public void sendToPlayer(ServerPlayer player, T message) {
- Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(toPacket(NetworkManager.s2c(), message));
+ Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(toPacket(NetworkManager.s2c(), message, player.registryAccess()));
}
public void sendToPlayers(Iterable players, T message) {
- var packet = toPacket(NetworkManager.s2c(), message);
+ Iterator iterator = players.iterator();
+ if (!iterator.hasNext()) return;
+ var packet = toPacket(NetworkManager.s2c(), message, iterator.next().registryAccess());
for (var player : players) {
Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet);
}
@@ -98,8 +104,9 @@ public final class NetworkChannel {
@Environment(EnvType.CLIENT)
public void sendToServer(T message) {
- if (Minecraft.getInstance().getConnection() != null) {
- Minecraft.getInstance().getConnection().send(toPacket(NetworkManager.c2s(), message));
+ ClientPacketListener connection = Minecraft.getInstance().getConnection();
+ if (connection != null) {
+ connection.send(toPacket(NetworkManager.c2s(), message, connection.registryAccess()));
} else {
throw new IllegalStateException("Unable to send packet to the server while not in game!");
}
diff --git a/common/src/main/java/dev/architectury/networking/NetworkManager.java b/common/src/main/java/dev/architectury/networking/NetworkManager.java
index 58c6c4ed..7a6e9c4f 100644
--- a/common/src/main/java/dev/architectury/networking/NetworkManager.java
+++ b/common/src/main/java/dev/architectury/networking/NetworkManager.java
@@ -19,16 +19,22 @@
package dev.architectury.networking;
+import dev.architectury.impl.NetworkAggregator;
import dev.architectury.injectables.annotations.ExpectPlatform;
import dev.architectury.networking.transformers.PacketCollector;
import dev.architectury.networking.transformers.PacketSink;
import dev.architectury.networking.transformers.PacketTransformer;
import dev.architectury.networking.transformers.SinglePacketCollector;
import dev.architectury.utils.Env;
+import dev.architectury.utils.GameInstance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
@@ -37,53 +43,112 @@ import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
public final class NetworkManager {
- public static void registerReceiver(Side side, ResourceLocation id, NetworkReceiver receiver) {
+ /**
+ * For S2C types, {@link #registerReceiver} should be called on the client side,
+ * while {@link #registerS2CPayloadType} should be called on the server side.
+ */
+ public static void registerS2CPayloadType(ResourceLocation id) {
+ NetworkAggregator.registerS2CType(id, List.of());
+ }
+
+ /**
+ * For S2C types, {@link #registerReceiver} should be called on the client side,
+ * while {@link #registerS2CPayloadType} should be called on the server side.
+ */
+ public static void registerS2CPayloadType(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec) {
+ NetworkAggregator.registerS2CType(type, codec, List.of());
+ }
+
+ /**
+ * For S2C types, {@link #registerReceiver} should be called on the client side,
+ * while {@link #registerS2CPayloadType} should be called on the server side.
+ */
+ public static void registerS2CPayloadType(ResourceLocation id, List packetTransformers) {
+ NetworkAggregator.registerS2CType(id, packetTransformers);
+ }
+
+ /**
+ * For S2C types, {@link #registerReceiver} should be called on the client side,
+ * while {@link #registerS2CPayloadType} should be called on the server side.
+ */
+ public static void registerS2CPayloadType(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers) {
+ NetworkAggregator.registerS2CType(type, codec, packetTransformers);
+ }
+
+ public static void registerReceiver(Side side, ResourceLocation id, NetworkReceiver receiver) {
registerReceiver(side, id, Collections.emptyList(), receiver);
}
- @ExpectPlatform
@ApiStatus.Experimental
- public static void registerReceiver(Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) {
- throw new AssertionError();
+ public static void registerReceiver(Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) {
+ NetworkAggregator.registerReceiver(side, id, packetTransformers, receiver);
+ }
+
+ public static void registerReceiver(Side side, CustomPacketPayload.Type id, StreamCodec super RegistryFriendlyByteBuf, T> codec, NetworkReceiver receiver) {
+ registerReceiver(side, id, codec, Collections.emptyList(), receiver);
+ }
+
+ @ApiStatus.Experimental
+ public static void registerReceiver(Side side, CustomPacketPayload.Type id, StreamCodec super RegistryFriendlyByteBuf, T> codec, List packetTransformers, NetworkReceiver receiver) {
+ NetworkAggregator.registerReceiver(side, id, codec, packetTransformers, receiver);
}
@Deprecated
- @ApiStatus.ScheduledForRemoval
- public static Packet> toPacket(Side side, ResourceLocation id, FriendlyByteBuf buf) {
+ public static Packet> toPacket(Side side, ResourceLocation id, RegistryFriendlyByteBuf buf) {
SinglePacketCollector sink = new SinglePacketCollector(null);
collectPackets(sink, side, id, buf);
return sink.getPacket();
}
@Deprecated
- @ApiStatus.ScheduledForRemoval
- public static List> toPackets(Side side, ResourceLocation id, FriendlyByteBuf buf) {
+ public static List> toPackets(Side side, ResourceLocation id, RegistryFriendlyByteBuf buf) {
PacketCollector sink = new PacketCollector(null);
collectPackets(sink, side, id, buf);
return sink.collect();
}
- @ExpectPlatform
- public static void collectPackets(PacketSink sink, Side side, ResourceLocation id, FriendlyByteBuf buf) {
- throw new AssertionError();
+ public static void collectPackets(PacketSink sink, Side side, ResourceLocation id, RegistryFriendlyByteBuf buf) {
+ NetworkAggregator.collectPackets(sink, side, id, buf);
}
- public static void sendToPlayer(ServerPlayer player, ResourceLocation id, FriendlyByteBuf buf) {
+ public static void collectPackets(PacketSink sink, Side side, T payload, RegistryAccess access) {
+ NetworkAggregator.collectPackets(sink, side, payload, access);
+ }
+
+ public static void sendToPlayer(ServerPlayer player, ResourceLocation id, RegistryFriendlyByteBuf buf) {
collectPackets(PacketSink.ofPlayer(player), serverToClient(), id, buf);
}
- public static void sendToPlayers(Iterable players, ResourceLocation id, FriendlyByteBuf buf) {
+ public static void sendToPlayers(Iterable players, ResourceLocation id, RegistryFriendlyByteBuf buf) {
collectPackets(PacketSink.ofPlayers(players), serverToClient(), id, buf);
}
@Environment(EnvType.CLIENT)
- public static void sendToServer(ResourceLocation id, FriendlyByteBuf buf) {
+ public static void sendToServer(ResourceLocation id, RegistryFriendlyByteBuf buf) {
collectPackets(PacketSink.client(), clientToServer(), id, buf);
}
+ public static void sendToPlayer(ServerPlayer player, T payload) {
+ collectPackets(PacketSink.ofPlayer(player), serverToClient(), payload, player.registryAccess());
+ }
+
+ public static void sendToPlayers(Iterable players, T payload) {
+ Iterator iterator = players.iterator();
+ if (!iterator.hasNext()) return;
+ collectPackets(PacketSink.ofPlayers(players), serverToClient(), payload, iterator.next().registryAccess());
+ }
+
+ @Environment(EnvType.CLIENT)
+ public static void sendToServer(T payload) {
+ ClientPacketListener connection = GameInstance.getClient().getConnection();
+ if (connection == null) return;
+ collectPackets(PacketSink.client(), clientToServer(), payload, connection.registryAccess());
+ }
+
@Environment(EnvType.CLIENT)
@ExpectPlatform
public static boolean canServerReceive(ResourceLocation id) {
@@ -112,9 +177,14 @@ public final class NetworkManager {
throw new AssertionError();
}
+ @ExpectPlatform
+ private static NetworkAggregator.Adaptor getAdaptor() {
+ throw new AssertionError();
+ }
+
@FunctionalInterface
- public interface NetworkReceiver {
- void receive(FriendlyByteBuf buf, PacketContext context);
+ public interface NetworkReceiver {
+ void receive(T value, PacketContext context);
}
public interface PacketContext {
@@ -124,6 +194,8 @@ public final class NetworkManager {
Env getEnvironment();
+ RegistryAccess registryAccess();
+
default EnvType getEnv() {
return getEnvironment().toPlatform();
}
diff --git a/common/src/main/java/dev/architectury/networking/SpawnEntityPacket.java b/common/src/main/java/dev/architectury/networking/SpawnEntityPacket.java
index c5b6e73d..d5e993f5 100644
--- a/common/src/main/java/dev/architectury/networking/SpawnEntityPacket.java
+++ b/common/src/main/java/dev/architectury/networking/SpawnEntityPacket.java
@@ -26,6 +26,7 @@ import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceLocation;
@@ -41,7 +42,7 @@ public class SpawnEntityPacket {
if (entity.level().isClientSide()) {
throw new IllegalStateException("SpawnPacketUtil.create called on the logical client!");
}
- var buffer = new FriendlyByteBuf(Unpooled.buffer());
+ var buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), entity.registryAccess());
buffer.writeVarInt(BuiltInRegistries.ENTITY_TYPE.getId(entity.getType()));
buffer.writeUUID(entity.getUUID());
buffer.writeVarInt(entity.getId());
@@ -62,6 +63,10 @@ public class SpawnEntityPacket {
return (Packet) NetworkManager.toPacket(NetworkManager.s2c(), PACKET_ID, buffer);
}
+ public static void register() {
+ NetworkManager.registerS2CPayloadType(PACKET_ID);
+ }
+
@Environment(EnvType.CLIENT)
public static class Client {
diff --git a/common/src/main/java/dev/architectury/networking/simple/BaseC2SMessage.java b/common/src/main/java/dev/architectury/networking/simple/BaseC2SMessage.java
index 79fa6331..7628ab32 100644
--- a/common/src/main/java/dev/architectury/networking/simple/BaseC2SMessage.java
+++ b/common/src/main/java/dev/architectury/networking/simple/BaseC2SMessage.java
@@ -33,7 +33,7 @@ public abstract class BaseC2SMessage extends Message {
@Environment(EnvType.CLIENT)
public final void sendToServer() {
if (Minecraft.getInstance().getConnection() != null) {
- Minecraft.getInstance().getConnection().send(toPacket());
+ Minecraft.getInstance().getConnection().send(toPacket(Minecraft.getInstance().level.registryAccess()));
} else {
throw new IllegalStateException("Unable to send packet to the server while not in game!");
}
diff --git a/common/src/main/java/dev/architectury/networking/simple/BaseS2CMessage.java b/common/src/main/java/dev/architectury/networking/simple/BaseS2CMessage.java
index a632f06f..d44c3927 100644
--- a/common/src/main/java/dev/architectury/networking/simple/BaseS2CMessage.java
+++ b/common/src/main/java/dev/architectury/networking/simple/BaseS2CMessage.java
@@ -44,7 +44,7 @@ public abstract class BaseS2CMessage extends Message {
* @param player the player
*/
public final void sendTo(ServerPlayer player) {
- sendTo(player, toPacket());
+ sendTo(player, toPacket(player.registryAccess()));
}
/**
@@ -53,7 +53,8 @@ public abstract class BaseS2CMessage extends Message {
* @param players the players
*/
public final void sendTo(Iterable players) {
- Packet> packet = toPacket();
+ if (!players.iterator().hasNext()) return;
+ Packet> packet = toPacket(players.iterator().next().registryAccess());
for (ServerPlayer player : players) {
sendTo(player, packet);
@@ -84,7 +85,7 @@ public abstract class BaseS2CMessage extends Message {
* @param chunk the listened chunk
*/
public final void sendToChunkListeners(LevelChunk chunk) {
- Packet> packet = toPacket();
+ Packet> packet = toPacket(chunk.getLevel().registryAccess());
((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false).forEach(e -> sendTo(e, packet));
}
}
\ No newline at end of file
diff --git a/common/src/main/java/dev/architectury/networking/simple/Message.java b/common/src/main/java/dev/architectury/networking/simple/Message.java
index 44fa2f80..49a400ad 100644
--- a/common/src/main/java/dev/architectury/networking/simple/Message.java
+++ b/common/src/main/java/dev/architectury/networking/simple/Message.java
@@ -21,7 +21,8 @@ package dev.architectury.networking.simple;
import dev.architectury.networking.NetworkManager;
import io.netty.buffer.Unpooled;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
/**
@@ -48,7 +49,7 @@ public abstract class Message {
*
* @param buf the byte buffer
*/
- public abstract void write(FriendlyByteBuf buf);
+ public abstract void write(RegistryFriendlyByteBuf buf);
/**
* Handles this message when it is received.
@@ -62,8 +63,8 @@ public abstract class Message {
*
* @return the converted {@link Packet}
*/
- public final Packet> toPacket() {
- FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ public final Packet> toPacket(RegistryAccess access) {
+ RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), access);
write(buf);
return NetworkManager.toPacket(getType().getSide(), getType().getId(), buf);
}
diff --git a/common/src/main/java/dev/architectury/networking/simple/MessageDecoder.java b/common/src/main/java/dev/architectury/networking/simple/MessageDecoder.java
index 36d399a4..b95fd20a 100644
--- a/common/src/main/java/dev/architectury/networking/simple/MessageDecoder.java
+++ b/common/src/main/java/dev/architectury/networking/simple/MessageDecoder.java
@@ -20,10 +20,10 @@
package dev.architectury.networking.simple;
import dev.architectury.networking.NetworkManager;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.RegistryFriendlyByteBuf;
/**
- * Decodes a {@link Message} from a {@link FriendlyByteBuf}.
+ * Decodes a {@link Message} from a {@link RegistryFriendlyByteBuf}.
*
* @param the message type handled by this decoder
* @author LatvianModder
@@ -36,17 +36,17 @@ public interface MessageDecoder {
* @param buf the byte buffer
* @return the decoded instance
*/
- T decode(FriendlyByteBuf buf);
+ T decode(RegistryFriendlyByteBuf buf);
/**
* Creates a network receiver from this decoder.
*
- * The returned receiver will first {@linkplain #decode(FriendlyByteBuf) decode a message}
+ *
The returned receiver will first {@linkplain #decode(RegistryFriendlyByteBuf) decode a message}
* and then call {@link Message#handle(NetworkManager.PacketContext)} on the decoded message.
*
* @return the created receiver
*/
- default NetworkManager.NetworkReceiver createReceiver() {
+ default NetworkManager.NetworkReceiver createReceiver() {
return (buf, context) -> {
Message packet = decode(buf);
context.queue(() -> packet.handle(context));
diff --git a/common/src/main/java/dev/architectury/networking/simple/SimpleNetworkManager.java b/common/src/main/java/dev/architectury/networking/simple/SimpleNetworkManager.java
index 7fdc4537..de2d9cff 100644
--- a/common/src/main/java/dev/architectury/networking/simple/SimpleNetworkManager.java
+++ b/common/src/main/java/dev/architectury/networking/simple/SimpleNetworkManager.java
@@ -23,6 +23,7 @@ import dev.architectury.networking.NetworkManager;
import dev.architectury.networking.transformers.PacketTransformer;
import dev.architectury.platform.Platform;
import dev.architectury.utils.Env;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
@@ -77,7 +78,7 @@ public class SimpleNetworkManager {
MessageType messageType = new MessageType(this, new ResourceLocation(namespace, id), NetworkManager.s2c());
if (Platform.getEnvironment() == Env.CLIENT) {
- NetworkManager.NetworkReceiver receiver = decoder.createReceiver();
+ NetworkManager.NetworkReceiver receiver = decoder.createReceiver();
NetworkManager.registerReceiver(NetworkManager.s2c(), messageType.getId(), transformers, receiver);
}
@@ -107,7 +108,7 @@ public class SimpleNetworkManager {
@ApiStatus.Experimental
public MessageType registerC2S(String id, MessageDecoder decoder, List transformers) {
MessageType messageType = new MessageType(this, new ResourceLocation(namespace, id), NetworkManager.c2s());
- NetworkManager.NetworkReceiver receiver = decoder.createReceiver();
+ NetworkManager.NetworkReceiver receiver = decoder.createReceiver();
NetworkManager.registerReceiver(NetworkManager.c2s(), messageType.getId(), transformers, receiver);
return messageType;
}
diff --git a/common/src/main/java/dev/architectury/networking/simple/package-info.java b/common/src/main/java/dev/architectury/networking/simple/package-info.java
new file mode 100644
index 00000000..e1b313ad
--- /dev/null
+++ b/common/src/main/java/dev/architectury/networking/simple/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@Deprecated(forRemoval = true)
+package dev.architectury.networking.simple;
\ No newline at end of file
diff --git a/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java b/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java
index 2b977970..64a34e2b 100644
--- a/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java
+++ b/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java
@@ -20,7 +20,7 @@
package dev.architectury.networking.transformers;
import dev.architectury.networking.NetworkManager;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -31,24 +31,24 @@ import java.util.List;
@ApiStatus.Experimental
public interface PacketTransformer {
- void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink);
+ void inbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink);
- void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink);
+ void outbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, TransformationSink sink);
@FunctionalInterface
interface TransformationSink {
- void accept(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf);
+ void accept(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf);
}
static PacketTransformer none() {
return new PacketTransformer() {
@Override
- public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
+ public void inbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
sink.accept(side, id, buf);
}
@Override
- public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) {
+ public void outbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, TransformationSink sink) {
sink.accept(side, id, buf);
}
};
@@ -62,16 +62,16 @@ public interface PacketTransformer {
}
return new PacketTransformer() {
@Override
- public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
+ public void inbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
traverse(side, id, buf, context, sink, true, 0);
}
@Override
- public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) {
+ public void outbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, TransformationSink sink) {
traverse(side, id, buf, null, sink, false, 0);
}
- private void traverse(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, @Nullable NetworkManager.PacketContext context, TransformationSink outerSink, boolean inbound, int index) {
+ private void traverse(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, @Nullable NetworkManager.PacketContext context, TransformationSink outerSink, boolean inbound, int index) {
if (transformers instanceof List) {
if (((List extends PacketTransformer>) transformers).size() > index) {
PacketTransformer transformer = ((List extends PacketTransformer>) transformers).get(index);
diff --git a/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java b/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java
index 56e4fa09..56b628a3 100644
--- a/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java
+++ b/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java
@@ -28,7 +28,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -81,7 +81,7 @@ public class SplitPacketTransformer implements PacketTransformer {
private static class PartData {
private final ResourceLocation id;
private final int partsExpected;
- private final List parts;
+ private final List parts;
public PartData(ResourceLocation id, int partsExpected) {
this.id = id;
@@ -109,7 +109,7 @@ public class SplitPacketTransformer implements PacketTransformer {
}
@Override
- public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
+ public void inbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) {
PartKey key = side == NetworkManager.Side.S2C ? new PartKey(side, null) : new PartKey(side, context.getPlayer().getUUID());
PartData data;
switch (buf.readByte()) {
@@ -128,7 +128,7 @@ public class SplitPacketTransformer implements PacketTransformer {
} else if (!data.id.equals(id)) {
LOGGER.warn("Received invalid PART packet for SplitPacketTransformer with packet id " + id + " for side " + side + ", id in cache is " + data.id);
buf.release();
- for (FriendlyByteBuf part : data.parts) {
+ for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
@@ -146,7 +146,7 @@ public class SplitPacketTransformer implements PacketTransformer {
} else if (!data.id.equals(id)) {
LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " for side " + side + ", id in cache is " + data.id);
buf.release();
- for (FriendlyByteBuf part : data.parts) {
+ for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
@@ -158,13 +158,13 @@ public class SplitPacketTransformer implements PacketTransformer {
}
if (data.parts.size() != data.partsExpected) {
LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " for side " + side + " with size " + data.parts + ", parts expected is " + data.partsExpected);
- for (FriendlyByteBuf part : data.parts) {
+ for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
} else {
- FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data.parts.toArray(new ByteBuf[0])));
+ RegistryFriendlyByteBuf byteBuf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(data.parts.toArray(new ByteBuf[0])), buf.registryAccess());
sink.accept(side, data.id, byteBuf);
byteBuf.release();
}
@@ -179,18 +179,18 @@ public class SplitPacketTransformer implements PacketTransformer {
}
@Override
- public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) {
+ public void outbound(NetworkManager.Side side, ResourceLocation id, RegistryFriendlyByteBuf buf, TransformationSink sink) {
int maxSize = (side == NetworkManager.Side.C2S ? 32767 : 1048576) - 1 - 20 - id.toString().getBytes(StandardCharsets.UTF_8).length;
if (buf.readableBytes() <= maxSize) {
ByteBuf stateBuf = Unpooled.buffer(1);
stateBuf.writeByte(ONLY);
- FriendlyByteBuf packetBuffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(stateBuf, buf));
+ RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(stateBuf, buf), buf.registryAccess());
sink.accept(side, id, packetBuffer);
} else {
int partSize = maxSize - 4;
int parts = (int) Math.ceil(buf.readableBytes() / (float) partSize);
for (int i = 0; i < parts; i++) {
- FriendlyByteBuf packetBuffer = new FriendlyByteBuf(Unpooled.buffer());
+ RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), buf.registryAccess());
if (i == 0) {
packetBuffer.writeByte(START);
packetBuffer.writeInt(parts);
diff --git a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
index fe15a0f6..e7f035f1 100644
--- a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
+++ b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
@@ -38,7 +38,7 @@ import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
-// import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
+import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
@@ -83,7 +83,7 @@ public class EventHandlerImpl {
AttackBlockCallback.EVENT.register((player, world, hand, pos, face) -> InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(player, hand, pos, face).asMinecraft());
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> PlayerEvent.ATTACK_ENTITY.invoker().attack(player, world, entity, hand, hitResult).asMinecraft());
- // LootTableEvents.MODIFY.register((key, tableBuilder, source) -> LootEvent.MODIFY_LOOT_TABLE.invoker().modifyLootTable(lootManager, id, new LootTableModificationContextImpl(tableBuilder), source.isBuiltin()));
+ LootTableEvents.MODIFY.register((key, tableBuilder, source) -> LootEvent.MODIFY_LOOT_TABLE.invoker().modifyLootTable(key, new LootTableModificationContextImpl(tableBuilder), source.isBuiltin()));
ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (player, component) -> {
ChatEvent.ChatComponent chatComponent = new ChatComponentImpl(component);
diff --git a/fabric/src/main/java/dev/architectury/fluid/fabric/FluidStackImpl.java b/fabric/src/main/java/dev/architectury/fluid/fabric/FluidStackImpl.java
index b0d6e7ad..46763a15 100644
--- a/fabric/src/main/java/dev/architectury/fluid/fabric/FluidStackImpl.java
+++ b/fabric/src/main/java/dev/architectury/fluid/fabric/FluidStackImpl.java
@@ -25,24 +25,28 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.architectury.fluid.FluidStack;
import io.netty.buffer.ByteBuf;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
+import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponentPatch;
+import net.minecraft.core.component.DataComponentType;
+import net.minecraft.core.component.PatchedDataComponentMap;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
-import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.material.FlowingFluid;
import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
@ApiStatus.Internal
-@SuppressWarnings("UnstableApiUsage")
public enum FluidStackImpl implements FluidStack.FluidStackAdapter {
INSTANCE;
@@ -60,13 +64,30 @@ public enum FluidStackImpl implements FluidStack.FluidStackAdapter getRawFluidSupplier(FluidStackImpl.Pair object) {
- return () -> object.variant.getFluid();
+ return () -> object.fluid;
}
@Override
public Fluid getFluid(FluidStackImpl.Pair object) {
- return object.variant.getFluid();
+ return object.fluid;
}
@Override
@@ -99,28 +120,60 @@ public enum FluidStackImpl implements FluidStack.FluidStackAdapter D set(Pair value, DataComponentType super D> type, @Nullable D component) {
+ return value.components.set(type, component);
+ }
+
+ @Override
+ @Nullable
+ public D remove(Pair value, DataComponentType extends D> type) {
+ return value.components.remove(type);
+ }
+
+ @Override
+ @Nullable
+ public D update(Pair value, DataComponentType type, D component, UnaryOperator updater) {
+ return value.components.set(type, updater.apply(getComponents(value).getOrDefault(type, component)));
+ }
+
+ @Override
+ @Nullable
+ public D update(Pair value, DataComponentType type, D component, U updateContext, BiFunction updater) {
+ return value.components.set(type, updater.apply(getComponents(value).getOrDefault(type, component), updateContext));
}
@Override
public FluidStackImpl.Pair copy(FluidStackImpl.Pair value) {
- return new Pair(value.variant, value.amount);
+ return new Pair(value.fluid, value.components.copy(), value.amount);
}
@Override
public int hashCode(FluidStackImpl.Pair value) {
var pair = (Pair) value;
var code = 1;
- code = 31 * code + pair.variant.hashCode();
+ code = 31 * code + pair.fluid.hashCode();
code = 31 * code + Long.hashCode(pair.amount);
- var patch = pair.variant.getComponents();
- if (patch != null)
- code = 31 * code + patch.hashCode();
+ code = 31 * code + pair.components.hashCode();
return code;
}
@@ -133,7 +186,7 @@ public enum FluidStackImpl implements FluidStack.FluidStackAdapter "Value must be non-negative: " + value);
}).fieldOf("amount").forGetter(FluidStack::getAmount),
- DataComponentPatch.CODEC.fieldOf("components").forGetter(FluidStack::getPatch)
+ DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(FluidStack::getPatch)
).apply(instance, FluidStack::create));
}
diff --git a/fabric/src/main/java/dev/architectury/hooks/fluid/fabric/FluidStackHooksFabric.java b/fabric/src/main/java/dev/architectury/hooks/fluid/fabric/FluidStackHooksFabric.java
index 8f395c36..a509d502 100644
--- a/fabric/src/main/java/dev/architectury/hooks/fluid/fabric/FluidStackHooksFabric.java
+++ b/fabric/src/main/java/dev/architectury/hooks/fluid/fabric/FluidStackHooksFabric.java
@@ -24,7 +24,6 @@ import dev.architectury.fluid.fabric.FluidStackImpl;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
-@SuppressWarnings("UnstableApiUsage")
public final class FluidStackHooksFabric {
private FluidStackHooksFabric() {
}
@@ -34,10 +33,10 @@ public final class FluidStackHooksFabric {
}
public static FluidStack fromFabric(FluidVariant variant, long amount) {
- return FluidStackImpl.fromValue.apply(new FluidStackImpl.Pair(variant, amount));
+ return FluidStackImpl.fromValue.apply(new FluidStackImpl.Pair(variant.getFluid(), variant.getComponents(), amount));
}
public static FluidVariant toFabric(FluidStack stack) {
- return ((FluidStackImpl.Pair) FluidStackImpl.toValue.apply(stack)).variant;
+ return ((FluidStackImpl.Pair) FluidStackImpl.toValue.apply(stack)).toVariant();
}
}
\ No newline at end of file
diff --git a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
index 023b2a95..f872095a 100644
--- a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
+++ b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
@@ -20,20 +20,19 @@
package dev.architectury.networking.fabric;
import com.mojang.logging.LogUtils;
+import dev.architectury.impl.NetworkAggregator;
import dev.architectury.networking.NetworkManager;
import dev.architectury.networking.NetworkManager.NetworkReceiver;
import dev.architectury.networking.SpawnEntityPacket;
-import dev.architectury.networking.transformers.PacketSink;
-import dev.architectury.networking.transformers.PacketTransformer;
import dev.architectury.utils.Env;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.buffer.Unpooled;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
-import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
@@ -44,79 +43,47 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.slf4j.Logger;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
public class NetworkManagerImpl {
- private static final Map> C2S_TYPE = new HashMap<>();
- private static final Map> S2C_TYPE = new HashMap<>();
- private static final Map C2S_RECEIVER = new HashMap<>();
- private static final Map S2C_RECEIVER = new HashMap<>();
- private static final Map C2S_TRANSFORMERS = new HashMap<>();
- private static final Map S2C_TRANSFORMERS = new HashMap<>();
-
private static final Logger LOGGER = LogUtils.getLogger();
- public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) {
- Objects.requireNonNull(id, "Cannot register receiver with a null ID!");
- packetTransformers = Objects.requireNonNullElse(packetTransformers, List.of());
- Objects.requireNonNull(receiver, "Cannot register a null receiver!");
- if (side == NetworkManager.Side.C2S) {
- registerC2SReceiver(id, packetTransformers, receiver);
- } else if (side == NetworkManager.Side.S2C) {
- registerS2CReceiver(id, packetTransformers, receiver);
- }
- }
-
- private static void registerC2SReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) {
- LOGGER.info("Registering C2S receiver with id {}", id);
- C2S_RECEIVER.put(id, receiver);
- CustomPacketPayload.Type type = new CustomPacketPayload.Type<>(id);
- C2S_TYPE.put(id, type);
- PayloadTypeRegistry.playC2S().register(type, BufCustomPacketPayload.streamCodec(type));
- PacketTransformer transformer = PacketTransformer.concat(packetTransformers);
- ServerPlayNetworking.registerGlobalReceiver(type, (payload, fabricContext) -> {
- var context = context(fabricContext.player(), fabricContext.player().server, false);
- FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.payload()));
- transformer.inbound(NetworkManager.Side.C2S, id, buf, context, (side, id1, buf1) -> {
- NetworkReceiver networkReceiver = side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1);
- if (networkReceiver == null) {
- throw new IllegalArgumentException("Network Receiver not found! " + id1);
- }
- networkReceiver.receive(buf1, context);
- });
- buf.release();
- });
- C2S_TRANSFORMERS.put(id, transformer);
- }
-
- @SuppressWarnings("Convert2Lambda")
- @Environment(EnvType.CLIENT)
- private static void registerS2CReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) {
- LOGGER.info("Registering S2C receiver with id {}", id);
- S2C_RECEIVER.put(id, receiver);
- CustomPacketPayload.Type type = new CustomPacketPayload.Type<>(id);
- S2C_TYPE.put(id, type);
- PayloadTypeRegistry.playS2C().register(type, BufCustomPacketPayload.streamCodec(type));
- PacketTransformer transformer = PacketTransformer.concat(packetTransformers);
- ClientPlayNetworking.registerGlobalReceiver(type, new ClientPlayNetworking.PlayPayloadHandler<>() {
+ public static NetworkAggregator.Adaptor getAdaptor() {
+ return new NetworkAggregator.Adaptor() {
@Override
- public void receive(BufCustomPacketPayload payload, ClientPlayNetworking.Context fabricContext) {
- var context = context(fabricContext.player(), fabricContext.client(), true);
- FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.payload()));
- transformer.inbound(NetworkManager.Side.S2C, id, buf, context, (side, id1, buf1) -> {
- NetworkReceiver networkReceiver = side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1);
- if (networkReceiver == null) {
- throw new IllegalArgumentException("Network Receiver not found! " + id1);
- }
- networkReceiver.receive(buf1, context);
+ public void registerC2S(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, NetworkReceiver receiver) {
+ LOGGER.info("Registering C2S receiver with id {}", type.id());
+ PayloadTypeRegistry.playC2S().register(type, codec);
+ ServerPlayNetworking.registerGlobalReceiver(type, (payload, fabricContext) -> {
+ var context = context(fabricContext.player(), fabricContext.player().server, false);
+ receiver.receive(payload, context);
});
- buf.release();
}
- });
- S2C_TRANSFORMERS.put(id, transformer);
+
+ @Override
+ @Environment(EnvType.CLIENT)
+ public void registerS2C(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec, NetworkReceiver receiver) {
+ LOGGER.info("Registering S2C receiver with id {}", type.id());
+ PayloadTypeRegistry.playS2C().register(type, codec);
+ ClientPlayNetworking.registerGlobalReceiver(type, (payload, fabricContext) -> {
+ var context = context(fabricContext.player(), fabricContext.client(), true);
+ receiver.receive(payload, context);
+ });
+ }
+
+ @Override
+ public Packet> toC2SPacket(T payload) {
+ return ClientPlayNetworking.createC2SPacket(payload);
+ }
+
+ @Override
+ public Packet> toS2CPacket(T payload) {
+ return ServerPlayNetworking.createS2CPacket(payload);
+ }
+
+ @Override
+ public void registerS2CType(CustomPacketPayload.Type type, StreamCodec super RegistryFriendlyByteBuf, T> codec) {
+ PayloadTypeRegistry.playS2C().register(type, codec);
+ }
+ };
}
private static NetworkManager.PacketContext context(Player player, BlockableEventLoop> taskQueue, boolean client) {
@@ -135,30 +102,14 @@ public class NetworkManagerImpl {
public Env getEnvironment() {
return client ? Env.CLIENT : Env.SERVER;
}
+
+ @Override
+ public RegistryAccess registryAccess() {
+ return player.registryAccess();
+ }
};
}
- public static void collectPackets(PacketSink sink, NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf) {
- PacketTransformer transformer = side == NetworkManager.Side.C2S ? C2S_TRANSFORMERS.get(id) : S2C_TRANSFORMERS.get(id);
- if (transformer != null) {
- transformer.outbound(side, id, buf, (side1, id1, buf1) -> {
- sink.accept(toPacket(side1, id1, buf1));
- });
- } else {
- sink.accept(toPacket(side, id, buf));
- }
- }
-
- public static Packet> toPacket(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf) {
- if (side == NetworkManager.Side.C2S) {
- return toC2SPacket(id, buf);
- } else if (side == NetworkManager.Side.S2C) {
- return toS2CPacket(id, buf);
- }
-
- throw new IllegalArgumentException("Invalid side: " + side);
- }
-
@Environment(EnvType.CLIENT)
public static boolean canServerReceive(ResourceLocation id) {
return ClientPlayNetworking.canSend(id);
@@ -171,21 +122,4 @@ public class NetworkManagerImpl {
public static Packet createAddEntityPacket(Entity entity) {
return SpawnEntityPacket.create(entity);
}
-
- @Environment(EnvType.CLIENT)
- private static Packet> toC2SPacket(ResourceLocation id, FriendlyByteBuf buf) {
- CustomPacketPayload.Type type = C2S_TYPE.get(id);
- if (type == null) {
- throw new IllegalArgumentException("Unknown packet id: " + id);
- }
- return ClientPlayNetworking.createC2SPacket(new BufCustomPacketPayload(type, ByteBufUtil.getBytes(buf)));
- }
-
- private static Packet> toS2CPacket(ResourceLocation id, FriendlyByteBuf buf) {
- CustomPacketPayload.Type type = S2C_TYPE.get(id);
- if (type == null) {
- throw new IllegalArgumentException("Unknown packet id: " + id);
- }
- return ServerPlayNetworking.createS2CPacket(new BufCustomPacketPayload(type, ByteBufUtil.getBytes(buf)));
- }
}
diff --git a/gradle.properties b/gradle.properties
index 511a225d..d68294d5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx6G
org.gradle.daemon=false
-platforms=fabric
+platforms=fabric,neoforge
minecraft_version=24w14a
supported_version=1.20.5 (24w14a)
@@ -18,7 +18,10 @@ fabric_api_version=0.96.14+1.20.5
mod_menu_version=9.0.0
forge_version=49.0.14
-neoforge_version=20.4.77-beta
+neoforge_version=20.5.0-alpha.24w14a.20240407.201521
+
+# Set to empty if not snapshots
+neoforge_pr=787
curseforge_id=419699
modrinth_id=lhGA9TYQ
diff --git a/neoforge/build.gradle b/neoforge/build.gradle
index e2cd2398..046d6703 100644
--- a/neoforge/build.gradle
+++ b/neoforge/build.gradle
@@ -11,38 +11,29 @@ architectury {
platformSetupLoomIde()
neoForge {
platformPackage = "forge"
- remapForgeLike "net/minecraftforge/common/extensions/IForgeItem", "net/neoforged/neoforge/common/extensions/IItemExtension"
- remapForgeLike "net/minecraftforge/client/event/TextureStitchEvent\$Post", "net/neoforged/neoforge/client/event/TextureAtlasStitchedEvent"
- remapForgeLike "net/minecraftforge/fluids/ForgeFlowingFluid", "net/neoforged/neoforge/fluids/BaseFlowingFluid"
- remapForgeLike "net/minecraftforge/fluids/ForgeFlowingFluid\$Properties", "net/neoforged/neoforge/fluids/BaseFlowingFluid\$Properties"
- remapForgeLike "net/minecraftforge/common/ForgeHooks", "net/neoforged/neoforge/common/CommonHooks"
}
}
configurations {
common
- forgeLike
shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this.
- compileClasspath.extendsFrom common, forgeLike
- runtimeClasspath.extendsFrom common, forgeLike
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
developmentNeoForge.extendsFrom common
- developmentForgeLike.extendsFrom forgeLike
}
dependencies {
neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}"
common(project(path: ":common", configuration: "namedElements")) { transitive false }
- forgeLike(project(path: ":forge", configuration: "namedElements")) { transitive false }
shadowCommon(project(path: ":common", configuration: "transformProductionNeoForge")) { transitive false }
- shadowCommon(project(path: ":forge", configuration: "transformProductionNeoForge")) { transitive false }
}
processResources {
- filesMatching("META-INF/mods.toml") {
+ filesMatching("META-INF/neoforge.mods.toml") {
expand "version": project.version
}
- inputs.property "META-INF/mods.toml", project.version
+ inputs.property "META-INF/neoforge.mods.toml", project.version
}
shadowJar {
@@ -113,7 +104,7 @@ publishing {
}
repositories {
- if (System.getenv("MAVEN_PASS") != null) {
+ if (System.getenv("MAVEN_PASS") != null && rootProject.neoforge_pr == "") {
maven {
url = "https://deploy.shedaniel.me/"
credentials {
@@ -126,6 +117,7 @@ publishing {
}
unifiedPublishing {
+ if (rootProject.neoforge_pr != "") return // Don't publish PRs
project {
displayName = "[NeoForge $rootProject.supported_version] v$project.version"
releaseType = "$rootProject.artifact_type"
diff --git a/neoforge/src/main/java/dev/architectury/core/block/forge/imitator/ArchitecturyLiquidBlock.java b/neoforge/src/main/java/dev/architectury/core/block/forge/imitator/ArchitecturyLiquidBlock.java
new file mode 100644
index 00000000..45daccce
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/core/block/forge/imitator/ArchitecturyLiquidBlock.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.core.block.forge.imitator;
+
+import net.minecraft.world.level.block.LiquidBlock;
+import net.minecraft.world.level.material.FlowingFluid;
+
+import java.util.function.Supplier;
+
+public class ArchitecturyLiquidBlock extends LiquidBlock {
+ public ArchitecturyLiquidBlock(Supplier extends FlowingFluid> fluid, Properties properties) {
+ super(fluid, properties);
+ }
+}
\ No newline at end of file
diff --git a/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFlowingFluid.java b/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFlowingFluid.java
new file mode 100644
index 00000000..f387c2fa
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFlowingFluid.java
@@ -0,0 +1,184 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.core.fluid.forge.imitator;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Suppliers;
+import dev.architectury.core.fluid.ArchitecturyFluidAttributes;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.LevelReader;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.LiquidBlock;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.block.state.StateDefinition;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.FluidState;
+import net.neoforged.neoforge.fluids.BaseFlowingFluid;
+import net.neoforged.neoforge.fluids.FluidType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public abstract class ArchitecturyFlowingFluid extends BaseFlowingFluid {
+ private static final Map FLUID_TYPE_MAP = new IdentityHashMap<>();
+ private final ArchitecturyFluidAttributes attributes;
+
+ ArchitecturyFlowingFluid(ArchitecturyFluidAttributes attributes) {
+ super(toForgeProperties(attributes));
+ this.attributes = attributes;
+ }
+
+ private static Properties toForgeProperties(ArchitecturyFluidAttributes attributes) {
+ Properties forge = new Properties(Suppliers.memoize(() -> {
+ return FLUID_TYPE_MAP.computeIfAbsent(attributes, attr -> {
+ return new ArchitecturyFluidAttributesForge(FluidType.Properties.create(), attr.getSourceFluid(), attr);
+ });
+ }), attributes::getSourceFluid, attributes::getFlowingFluid);
+ forge.slopeFindDistance(attributes.getSlopeFindDistance());
+ forge.levelDecreasePerBlock(attributes.getDropOff());
+ forge.bucket(() -> MoreObjects.firstNonNull(attributes.getBucketItem(), Items.AIR));
+ forge.tickRate(attributes.getTickDelay());
+ forge.explosionResistance(attributes.getExplosionResistance());
+ forge.block(() -> MoreObjects.firstNonNull(attributes.getBlock(), (LiquidBlock) Blocks.WATER));
+ return forge;
+ }
+
+ @Override
+ public Fluid getFlowing() {
+ return attributes.getFlowingFluid();
+ }
+
+ @Override
+ public Fluid getSource() {
+ return attributes.getSourceFluid();
+ }
+
+ @Override
+ protected boolean canConvertToSource(Level level) {
+ return attributes.canConvertToSource();
+ }
+
+ @Override
+ protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state) {
+ // Same implementation as in WaterFluid.
+ BlockEntity blockEntity = state.hasBlockEntity() ? level.getBlockEntity(pos) : null;
+ Block.dropResources(state, level, pos, blockEntity);
+ }
+
+ @Override
+ protected int getSlopeFindDistance(LevelReader level) {
+ return attributes.getSlopeFindDistance(level);
+ }
+
+ @Override
+ protected int getDropOff(LevelReader level) {
+ return attributes.getDropOff(level);
+ }
+
+ @Override
+ public Item getBucket() {
+ Item item = attributes.getBucketItem();
+ return item == null ? Items.AIR : item;
+ }
+
+ @Override
+ protected boolean canBeReplacedWith(FluidState state, BlockGetter level, BlockPos pos, Fluid fluid, Direction direction) {
+ // Same implementation as in WaterFluid.
+ return direction == Direction.DOWN && !this.isSame(fluid);
+ }
+
+ @Override
+ public int getTickDelay(LevelReader level) {
+ return attributes.getTickDelay(level);
+ }
+
+ @Override
+ protected float getExplosionResistance() {
+ return attributes.getExplosionResistance();
+ }
+
+ @Override
+ protected BlockState createLegacyBlock(FluidState state) {
+ LiquidBlock block = attributes.getBlock();
+ if (block == null) return Blocks.AIR.defaultBlockState();
+ return block.defaultBlockState().setValue(LiquidBlock.LEVEL, getLegacyLevel(state));
+ }
+
+ @NotNull
+ @Override
+ public Optional getPickupSound() {
+ return Optional.ofNullable(attributes.getFillSound());
+ }
+
+ @Override
+ public boolean isSame(Fluid fluid) {
+ return fluid == getSource() || fluid == getFlowing();
+ }
+
+ public static class Source extends ArchitecturyFlowingFluid {
+ public Source(ArchitecturyFluidAttributes attributes) {
+ super(attributes);
+ }
+
+ @Override
+ public int getAmount(FluidState state) {
+ return 8;
+ }
+
+ @Override
+ public boolean isSource(FluidState state) {
+ return true;
+ }
+ }
+
+ public static class Flowing extends ArchitecturyFlowingFluid {
+ public Flowing(ArchitecturyFluidAttributes attributes) {
+ super(attributes);
+ this.registerDefaultState(this.getStateDefinition().any().setValue(LEVEL, 7));
+ }
+
+ @Override
+ protected void createFluidStateDefinition(StateDefinition.Builder builder) {
+ super.createFluidStateDefinition(builder);
+ builder.add(LEVEL);
+ }
+
+ @Override
+ public int getAmount(FluidState state) {
+ return state.getValue(LEVEL);
+ }
+
+ @Override
+ public boolean isSource(FluidState state) {
+ return false;
+ }
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFluidAttributesForge.java b/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFluidAttributesForge.java
new file mode 100644
index 00000000..ca24d3b1
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/core/fluid/forge/imitator/ArchitecturyFluidAttributesForge.java
@@ -0,0 +1,265 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.core.fluid.forge.imitator;
+
+import com.google.common.base.MoreObjects;
+import dev.architectury.core.fluid.ArchitecturyFluidAttributes;
+import dev.architectury.hooks.fluid.forge.FluidStackHooksForge;
+import net.minecraft.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Rarity;
+import net.minecraft.world.level.BlockAndTintGetter;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.LevelReader;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.FluidState;
+import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
+import net.neoforged.neoforge.common.SoundAction;
+import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.FluidType;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Consumer;
+
+import static net.minecraft.sounds.SoundEvents.BUCKET_EMPTY;
+import static net.minecraft.sounds.SoundEvents.BUCKET_FILL;
+
+class ArchitecturyFluidAttributesForge extends FluidType {
+ private final ArchitecturyFluidAttributes attributes;
+ private final String defaultTranslationKey;
+
+ public ArchitecturyFluidAttributesForge(Properties builder, Fluid fluid, ArchitecturyFluidAttributes attributes) {
+ super(addArchIntoBuilder(builder, attributes));
+ this.attributes = attributes;
+ this.defaultTranslationKey = Util.makeDescriptionId("fluid", BuiltInRegistries.FLUID.getKey(fluid));
+ }
+
+ private static Properties addArchIntoBuilder(Properties builder, ArchitecturyFluidAttributes attributes) {
+ builder.lightLevel(attributes.getLuminosity())
+ .density(attributes.getDensity())
+ .temperature(attributes.getTemperature())
+ .rarity(attributes.getRarity())
+ .canConvertToSource(attributes.canConvertToSource())
+ .viscosity(attributes.getViscosity());
+ return builder;
+ }
+
+ @Override
+ public ItemStack getBucket(FluidStack stack) {
+ Item item = attributes.getBucketItem();
+ return item == null ? super.getBucket(stack) : new ItemStack(item);
+ }
+
+ @Override
+ public void initializeClient(Consumer consumer) {
+ consumer.accept(new IClientFluidTypeExtensions() {
+ @Override
+ public int getTintColor() {
+ return attributes.getColor();
+ }
+
+ @Override
+ public ResourceLocation getStillTexture() {
+ return attributes.getSourceTexture();
+ }
+
+ @Override
+ public ResourceLocation getFlowingTexture() {
+ return attributes.getFlowingTexture();
+ }
+
+ @Override
+ @Nullable
+ public ResourceLocation getOverlayTexture() {
+ return attributes.getOverlayTexture();
+ }
+
+ @Override
+ public ResourceLocation getStillTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) {
+ return attributes.getSourceTexture(state, getter, pos);
+ }
+
+ @Override
+ public ResourceLocation getFlowingTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) {
+ return attributes.getFlowingTexture(state, getter, pos);
+ }
+
+ @Override
+ @Nullable
+ public ResourceLocation getOverlayTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) {
+ return attributes.getOverlayTexture(state, getter, pos);
+ }
+
+ @Override
+ public int getTintColor(FluidState state, BlockAndTintGetter getter, BlockPos pos) {
+ return attributes.getColor(state, getter, pos);
+ }
+
+ @Override
+ public int getTintColor(FluidStack stack) {
+ return attributes.getColor(convertSafe(stack));
+ }
+
+ @Override
+ public ResourceLocation getStillTexture(FluidStack stack) {
+ return attributes.getSourceTexture(convertSafe(stack));
+ }
+
+ @Override
+ public ResourceLocation getFlowingTexture(FluidStack stack) {
+ return attributes.getFlowingTexture(convertSafe(stack));
+ }
+
+ @Override
+ @Nullable
+ public ResourceLocation getOverlayTexture(FluidStack stack) {
+ return attributes.getOverlayTexture(convertSafe(stack));
+ }
+ });
+ }
+
+ @Override
+ public int getLightLevel(FluidStack stack) {
+ return attributes.getLuminosity(convertSafe(stack));
+ }
+
+ @Override
+ public int getLightLevel(FluidState state, BlockAndTintGetter level, BlockPos pos) {
+ return attributes.getLuminosity(convertSafe(state), level, pos);
+ }
+
+ @Override
+ public int getDensity(FluidStack stack) {
+ return attributes.getDensity(convertSafe(stack));
+ }
+
+ @Override
+ public int getDensity(FluidState state, BlockAndTintGetter level, BlockPos pos) {
+ return attributes.getDensity(convertSafe(state), level, pos);
+ }
+
+ @Override
+ public int getTemperature(FluidStack stack) {
+ return attributes.getTemperature(convertSafe(stack));
+ }
+
+ @Override
+ public int getTemperature(FluidState state, BlockAndTintGetter level, BlockPos pos) {
+ return attributes.getTemperature(convertSafe(state), level, pos);
+ }
+
+ @Override
+ public int getViscosity(FluidStack stack) {
+ return attributes.getViscosity(convertSafe(stack));
+ }
+
+ @Override
+ public int getViscosity(FluidState state, BlockAndTintGetter level, BlockPos pos) {
+ return attributes.getViscosity(convertSafe(state), level, pos);
+ }
+
+ @Override
+ public Rarity getRarity() {
+ return attributes.getRarity();
+ }
+
+ @Override
+ public Rarity getRarity(FluidStack stack) {
+ return attributes.getRarity(convertSafe(stack));
+ }
+
+ @Override
+ public Component getDescription() {
+ return attributes.getName();
+ }
+
+ @Override
+ public Component getDescription(FluidStack stack) {
+ return attributes.getName(convertSafe(stack));
+ }
+
+ @Override
+ public String getDescriptionId() {
+ return MoreObjects.firstNonNull(attributes.getTranslationKey(), defaultTranslationKey);
+ }
+
+ @Override
+ public String getDescriptionId(FluidStack stack) {
+ return MoreObjects.firstNonNull(attributes.getTranslationKey(convertSafe(stack)), defaultTranslationKey);
+ }
+
+ @Override
+ @Nullable
+ public SoundEvent getSound(SoundAction action) {
+ return getSound((FluidStack) null, action);
+ }
+
+ @Override
+ @Nullable
+ public SoundEvent getSound(@Nullable FluidStack stack, SoundAction action) {
+ var archStack = convertSafe(stack);
+ if (BUCKET_FILL.equals(action)) {
+ return attributes.getFillSound(archStack);
+ } else if (BUCKET_EMPTY.equals(action)) {
+ return attributes.getEmptySound(archStack);
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public SoundEvent getSound(@Nullable Player player, BlockGetter getter, BlockPos pos, SoundAction action) {
+ if (getter instanceof BlockAndTintGetter level) {
+ if (BUCKET_FILL.equals(action)) {
+ return attributes.getFillSound(null, level, pos);
+ } else if (BUCKET_EMPTY.equals(action)) {
+ return attributes.getEmptySound(null, level, pos);
+ }
+ }
+ return getSound((FluidStack) null, action);
+ }
+
+ @Override
+ public boolean canConvertToSource(FluidStack stack) {
+ return attributes.canConvertToSource();
+ }
+
+ @Override
+ public boolean canConvertToSource(FluidState state, LevelReader reader, BlockPos pos) {
+ return attributes.canConvertToSource();
+ }
+
+ @Nullable
+ public dev.architectury.fluid.FluidStack convertSafe(@Nullable FluidStack stack) {
+ return stack == null ? null : FluidStackHooksForge.fromForge(stack);
+ }
+
+ @Nullable
+ public dev.architectury.fluid.FluidStack convertSafe(@Nullable FluidState state) {
+ return state == null ? null : dev.architectury.fluid.FluidStack.create(state.getType(), dev.architectury.fluid.FluidStack.bucketAmount());
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/hooks/forgelike/forge/ForgeLikeHooksImpl.java b/neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyBucketItem.java
similarity index 53%
rename from neoforge/src/main/java/dev/architectury/hooks/forgelike/forge/ForgeLikeHooksImpl.java
rename to neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyBucketItem.java
index 6d07b53a..35320487 100644
--- a/neoforge/src/main/java/dev/architectury/hooks/forgelike/forge/ForgeLikeHooksImpl.java
+++ b/neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyBucketItem.java
@@ -17,47 +17,38 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package dev.architectury.hooks.forgelike.forge;
+package dev.architectury.core.item.forge.imitator;
-import com.mojang.serialization.Codec;
-import dev.architectury.platform.hooks.forge.EventBusesHooksImpl;
+import dev.architectury.platform.hooks.EventBusesHooks;
import dev.architectury.utils.ArchitecturyConstants;
import net.minecraft.core.registries.BuiltInRegistries;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.item.Item;
+import net.minecraft.world.item.BucketItem;
+import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
-import net.neoforged.neoforge.common.world.BiomeModifier;
import net.neoforged.neoforge.fluids.capability.wrappers.FluidBucketWrapper;
-import net.neoforged.neoforge.registries.NeoForgeRegistries;
-import net.neoforged.neoforge.registries.RegisterEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.Supplier;
-public class ForgeLikeHooksImpl {
- private static final Logger LOGGER = LogManager.getLogger(ForgeLikeHooksImpl.class);
+public class ArchitecturyBucketItem extends BucketItem {
+ private static final Logger LOGGER = LogManager.getLogger(ArchitecturyBucketItem.class);
- public static void registerBiomeModifier(ResourceLocation id, Supplier> codecSupplier) {
- EventBusesHooksImpl.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
- bus.addListener(event -> {
- event.register(NeoForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, registry -> {
- registry.register(id, codecSupplier.get());
- });
- });
- });
- }
-
- public static void registerBucketItemCapability(Item item) {
- EventBusesHooksImpl.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
+ public ArchitecturyBucketItem(Supplier extends Fluid> fluid, Properties properties) {
+ super(fluid, properties);
+ EventBusesHooks.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
bus.addListener(event -> {
- if (BuiltInRegistries.ITEM.containsValue(item)) {
- event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), item);
+ if (BuiltInRegistries.ITEM.containsValue(this)) {
+ event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), this);
} else {
- LOGGER.warn("Tried to register a bucket item capability for an item that is not registered: {}", item);
+ LOGGER.warn("Tried to register a bucket item capability for an item that is not registered: {}", this);
}
});
});
}
+
+ public final Fluid getContainedFluid() {
+ return getFluid();
+ }
}
diff --git a/neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyMobBucketItem.java b/neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyMobBucketItem.java
new file mode 100644
index 00000000..3a484a2a
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/core/item/forge/imitator/ArchitecturyMobBucketItem.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.core.item.forge.imitator;
+
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.MobBucketItem;
+import net.minecraft.world.level.material.Fluid;
+
+import java.util.function.Supplier;
+
+public class ArchitecturyMobBucketItem extends MobBucketItem {
+ public ArchitecturyMobBucketItem(Supplier extends EntityType>> entity, Supplier extends Fluid> fluid, Supplier extends SoundEvent> sound, Properties properties) {
+ super(entity, fluid, sound, properties);
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/EventFactoryImpl.java b/neoforge/src/main/java/dev/architectury/event/forge/EventFactoryImpl.java
new file mode 100644
index 00000000..2b7feaef
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/EventFactoryImpl.java
@@ -0,0 +1,73 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import dev.architectury.event.Event;
+import dev.architectury.event.EventActor;
+import dev.architectury.event.EventResult;
+import net.neoforged.bus.api.ICancellableEvent;
+import net.neoforged.neoforge.common.NeoForge;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.function.Consumer;
+
+public class EventFactoryImpl {
+ public static Event> attachToForge(Event> event) {
+ event.register(eventObj -> {
+ if (!(eventObj instanceof net.neoforged.bus.api.Event)) {
+ throw new ClassCastException(eventObj.getClass() + " is not an instance of forge Event!");
+ }
+ NeoForge.EVENT_BUS.post((net.neoforged.bus.api.Event) eventObj);
+ });
+ return event;
+ }
+
+ @ApiStatus.Internal
+ public static Event> attachToForgeEventActor(Event> event) {
+ event.register(eventObj -> {
+ if (!(eventObj instanceof net.neoforged.bus.api.Event)) {
+ throw new ClassCastException(eventObj.getClass() + " is not an instance of forge Event!");
+ }
+ if (!(eventObj instanceof ICancellableEvent)) {
+ throw new ClassCastException(eventObj.getClass() + " is not cancellable Event!");
+ }
+ NeoForge.EVENT_BUS.post((net.neoforged.bus.api.Event) eventObj);
+ return EventResult.pass();
+ });
+ return event;
+ }
+
+ @ApiStatus.Internal
+ public static Event> attachToForgeEventActorCancellable(Event> event) {
+ event.register(eventObj -> {
+ if (!(eventObj instanceof net.neoforged.bus.api.Event)) {
+ throw new ClassCastException(eventObj.getClass() + " is not an instance of forge Event!");
+ }
+ if (!(eventObj instanceof ICancellableEvent)) {
+ throw new ClassCastException(eventObj.getClass() + " is not cancellable Event!");
+ }
+ if (((ICancellableEvent) NeoForge.EVENT_BUS.post((net.neoforged.bus.api.Event) eventObj)).isCanceled()) {
+ return EventResult.interrupt(false);
+ }
+ return EventResult.pass();
+ });
+ return event;
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImpl.java b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImpl.java
new file mode 100644
index 00000000..0a2f6b18
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImpl.java
@@ -0,0 +1,51 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import dev.architectury.platform.hooks.EventBusesHooks;
+import dev.architectury.utils.ArchitecturyConstants;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.api.distmarker.OnlyIn;
+import net.neoforged.neoforge.common.NeoForge;
+
+public class EventHandlerImpl {
+ @OnlyIn(Dist.CLIENT)
+ public static void registerClient() {
+ NeoForge.EVENT_BUS.register(EventHandlerImplClient.class);
+ EventBusesHooks.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
+ bus.register(EventHandlerImplClient.ModBasedEventHandler.class);
+ });
+ }
+
+ public static void registerCommon() {
+ NeoForge.EVENT_BUS.register(EventHandlerImplCommon.class);
+ EventBusesHooks.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
+ bus.register(EventHandlerImplCommon.ModBasedEventHandler.class);
+ });
+ }
+
+ @OnlyIn(Dist.DEDICATED_SERVER)
+ public static void registerServer() {
+ // MinecraftForge.EVENT_BUS.register(EventHandlerImplServer.class);
+ // EventBusesHooks.whenAvailable(ArchitecturyConstants.MOD_ID, bus -> {
+ // bus.register(EventHandlerImplServer.ModBasedEventHandler.class);
+ // });
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java
new file mode 100644
index 00000000..a2ac26a1
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplClient.java
@@ -0,0 +1,351 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import com.mojang.brigadier.CommandDispatcher;
+import dev.architectury.event.CompoundEventResult;
+import dev.architectury.event.EventResult;
+import dev.architectury.event.events.client.ClientChatEvent;
+import dev.architectury.event.events.client.*;
+import dev.architectury.event.events.common.InteractionEvent;
+import dev.architectury.impl.ScreenAccessImpl;
+import dev.architectury.impl.TooltipEventColorContextImpl;
+import dev.architectury.impl.TooltipEventPositionContextImpl;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.network.chat.Component;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.api.distmarker.OnlyIn;
+import net.neoforged.bus.api.EventPriority;
+import net.neoforged.bus.api.SubscribeEvent;
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
+import net.neoforged.neoforge.client.event.*;
+import net.neoforged.neoforge.event.TickEvent;
+import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent;
+import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
+import net.neoforged.neoforge.event.level.LevelEvent;
+
+@OnlyIn(Dist.CLIENT)
+public class EventHandlerImplClient {
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ItemTooltipEvent event) {
+ ClientTooltipEvent.ITEM.invoker().append(event.getItemStack(), event.getToolTip(), event.getFlags());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(TickEvent.ClientTickEvent event) {
+ if (event.phase == TickEvent.Phase.START)
+ ClientTickEvent.CLIENT_PRE.invoker().tick(Minecraft.getInstance());
+ else if (event.phase == TickEvent.Phase.END)
+ ClientTickEvent.CLIENT_POST.invoker().tick(Minecraft.getInstance());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventRenderGameOverlayEvent(RenderGuiEvent.Post event) {
+ ClientGuiEvent.RENDER_HUD.invoker().renderHud(event.getGuiGraphics(), event.getPartialTick());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ClientPlayerNetworkEvent.LoggingIn event) {
+ ClientPlayerEvent.CLIENT_PLAYER_JOIN.invoker().join(event.getPlayer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ClientPlayerNetworkEvent.LoggingOut event) {
+ ClientPlayerEvent.CLIENT_PLAYER_QUIT.invoker().quit(event.getPlayer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ClientPlayerNetworkEvent.Clone event) {
+ ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.invoker().respawn(event.getOldPlayer(), event.getNewPlayer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInitScreenEvent(ScreenEvent.Init.Pre event) {
+ if (ClientGuiEvent.INIT_PRE.invoker().init(event.getScreen(), new ScreenAccessImpl(event.getScreen())).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInitScreenEvent(ScreenEvent.Init.Post event) {
+ ClientGuiEvent.INIT_POST.invoker().init(event.getScreen(), new ScreenAccessImpl(event.getScreen()));
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventRenderGameOverlayEvent(CustomizeGuiOverlayEvent.DebugText event) {
+ if (Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) {
+ ClientGuiEvent.DEBUG_TEXT_LEFT.invoker().gatherText(event.getLeft());
+ ClientGuiEvent.DEBUG_TEXT_RIGHT.invoker().gatherText(event.getRight());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(net.neoforged.neoforge.client.event.ClientChatEvent event) {
+ EventResult process = ClientChatEvent.SEND.invoker().send(event.getMessage(), null);
+ if (process.isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ClientChatReceivedEvent event) {
+ CompoundEventResult process = ClientChatEvent.RECEIVED.invoker().process(event.getBoundChatType(), event.getMessage());
+ if (process.isPresent()) {
+ if (process.isFalse())
+ event.setCanceled(true);
+ else if (process.object() != null)
+ event.setMessage(process.object());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventWorldEvent(LevelEvent.Load event) {
+ if (event.getLevel().isClientSide()) {
+ ClientLevel world = (ClientLevel) event.getLevel();
+ ClientLifecycleEvent.CLIENT_LEVEL_LOAD.invoker().act(world);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ScreenEvent.Opening event) {
+ CompoundEventResult result = ClientGuiEvent.SET_SCREEN.invoker().modifyScreen(event.getScreen());
+ if (result.isPresent()) {
+ if (result.isFalse())
+ event.setCanceled(true);
+ else if (result.object() != null)
+ event.setNewScreen(result.object());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventDrawScreenEvent(ScreenEvent.Render.Pre event) {
+ if (ClientGuiEvent.RENDER_PRE.invoker().render(event.getScreen(), event.getGuiGraphics(), event.getMouseX(), event.getMouseY(), event.getPartialTick()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventDrawScreenEvent(ScreenEvent.Render.Post event) {
+ ClientGuiEvent.RENDER_POST.invoker().render(event.getScreen(), event.getGuiGraphics(), event.getMouseX(), event.getMouseY(), event.getPartialTick());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventContainerScreenEvent(ContainerScreenEvent.Render.Background event) {
+ ClientGuiEvent.RENDER_CONTAINER_BACKGROUND.invoker().render(event.getContainerScreen(), event.getGuiGraphics(), event.getMouseX(), event.getMouseY(), Minecraft.getInstance().getDeltaFrameTime());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventContainerScreenEvent(ContainerScreenEvent.Render.Foreground event) {
+ ClientGuiEvent.RENDER_CONTAINER_FOREGROUND.invoker().render(event.getContainerScreen(), event.getGuiGraphics(), event.getMouseX(), event.getMouseY(), Minecraft.getInstance().getDeltaFrameTime());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.RightClickEmpty event) {
+ InteractionEvent.CLIENT_RIGHT_CLICK_AIR.invoker().click(event.getEntity(), event.getHand());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.LeftClickEmpty event) {
+ InteractionEvent.CLIENT_LEFT_CLICK_AIR.invoker().click(event.getEntity(), event.getHand());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(RecipesUpdatedEvent event) {
+ ClientRecipeUpdateEvent.EVENT.invoker().update(event.getRecipeManager());
+ }
+
+ private static final ThreadLocal tooltipPositionContext = ThreadLocal.withInitial(TooltipEventPositionContextImpl::new);
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventRenderTooltipEvent(RenderTooltipEvent.Pre event) {
+ GuiGraphics graphics = event.getGraphics();
+ ClientTooltipEvent.additionalContexts().setItem(event.getItemStack());
+
+ try {
+ if (ClientTooltipEvent.RENDER_PRE.invoker().renderTooltip(graphics, event.getComponents(), event.getX(), event.getY()).isFalse()) {
+ event.setCanceled(true);
+ return;
+ }
+
+ TooltipEventPositionContextImpl positionContext = tooltipPositionContext.get();
+ positionContext.reset(event.getX(), event.getY());
+ ClientTooltipEvent.RENDER_MODIFY_POSITION.invoker().renderTooltip(graphics, positionContext);
+ event.setX(positionContext.getTooltipX());
+ event.setY(positionContext.getTooltipY());
+ } finally {
+ ClientTooltipEvent.additionalContexts().setItem(null);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventRenderTooltipEvent(RenderTooltipEvent.Color event) {
+ GuiGraphics graphics = event.getGraphics();
+ ClientTooltipEvent.additionalContexts().setItem(event.getItemStack());
+
+ try {
+ TooltipEventColorContextImpl colorContext = TooltipEventColorContextImpl.CONTEXT.get();
+ colorContext.reset();
+ colorContext.setBackgroundColor(event.getBackgroundStart());
+ colorContext.setOutlineGradientTopColor(event.getBorderStart());
+ colorContext.setOutlineGradientBottomColor(event.getBorderEnd());
+ ClientTooltipEvent.RENDER_MODIFY_COLOR.invoker().renderTooltip(graphics, event.getX(), event.getY(), colorContext);
+ event.setBackground(colorContext.getBackgroundColor());
+ event.setBorderEnd(colorContext.getOutlineGradientBottomColor());
+ event.setBorderStart(colorContext.getOutlineGradientTopColor());
+ } finally {
+ ClientTooltipEvent.additionalContexts().setItem(null);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseScrollEvent(ScreenEvent.MouseScrolled.Pre event) {
+ if (ClientScreenInputEvent.MOUSE_SCROLLED_PRE.invoker().mouseScrolled(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getScrollDeltaX(), event.getScrollDeltaY()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseScrollEvent(ScreenEvent.MouseScrolled.Post event) {
+ ClientScreenInputEvent.MOUSE_SCROLLED_POST.invoker().mouseScrolled(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getScrollDeltaX(), event.getScrollDeltaY());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseClickedEvent(ScreenEvent.MouseButtonPressed.Pre event) {
+ if (ClientScreenInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getButton()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseClickedEvent(ScreenEvent.MouseButtonPressed.Post event) {
+ ClientScreenInputEvent.MOUSE_CLICKED_POST.invoker().mouseClicked(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getButton());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseDragEvent(ScreenEvent.MouseDragged.Pre event) {
+ if (ClientScreenInputEvent.MOUSE_DRAGGED_PRE.invoker().mouseDragged(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getMouseButton(), event.getDragX(), event.getDragY()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseDragEvent(ScreenEvent.MouseDragged.Post event) {
+ ClientScreenInputEvent.MOUSE_DRAGGED_POST.invoker().mouseDragged(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getMouseButton(), event.getDragX(), event.getDragY());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseReleasedEvent(ScreenEvent.MouseButtonReleased.Pre event) {
+ if (ClientScreenInputEvent.MOUSE_RELEASED_PRE.invoker().mouseReleased(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getButton()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventMouseReleasedEvent(ScreenEvent.MouseButtonReleased.Post event) {
+ ClientScreenInputEvent.MOUSE_RELEASED_PRE.invoker().mouseReleased(Minecraft.getInstance(), event.getScreen(), event.getMouseX(), event.getMouseY(), event.getButton());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardCharTypedEvent(ScreenEvent.CharacterTyped.Pre event) {
+ if (ClientScreenInputEvent.CHAR_TYPED_PRE.invoker().charTyped(Minecraft.getInstance(), event.getScreen(), event.getCodePoint(), event.getModifiers()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardCharTypedEvent(ScreenEvent.CharacterTyped.Post event) {
+ ClientScreenInputEvent.CHAR_TYPED_POST.invoker().charTyped(Minecraft.getInstance(), event.getScreen(), event.getCodePoint(), event.getModifiers());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardKeyPressedEvent(ScreenEvent.KeyPressed.Pre event) {
+ if (ClientScreenInputEvent.KEY_PRESSED_PRE.invoker().keyPressed(Minecraft.getInstance(), event.getScreen(), event.getKeyCode(), event.getScanCode(), event.getModifiers()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardKeyPressedEvent(ScreenEvent.KeyPressed.Post event) {
+ ClientScreenInputEvent.KEY_PRESSED_POST.invoker().keyPressed(Minecraft.getInstance(), event.getScreen(), event.getKeyCode(), event.getScanCode(), event.getModifiers());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardKeyReleasedEvent(ScreenEvent.KeyReleased.Pre event) {
+ if (ClientScreenInputEvent.KEY_RELEASED_PRE.invoker().keyReleased(Minecraft.getInstance(), event.getScreen(), event.getKeyCode(), event.getScanCode(), event.getModifiers()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventKeyboardKeyReleasedEvent(ScreenEvent.KeyReleased.Post event) {
+ ClientScreenInputEvent.KEY_RELEASED_POST.invoker().keyReleased(Minecraft.getInstance(), event.getScreen(), event.getKeyCode(), event.getScanCode(), event.getModifiers());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInputEvent(InputEvent.MouseScrollingEvent event) {
+ if (ClientRawInputEvent.MOUSE_SCROLLED.invoker().mouseScrolled(Minecraft.getInstance(), event.getScrollDeltaX(), event.getScrollDeltaY()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInputEvent(InputEvent.MouseButton.Pre event) {
+ if (ClientRawInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getButton(), event.getAction(), event.getModifiers()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInputEvent(InputEvent.MouseButton.Post event) {
+ ClientRawInputEvent.MOUSE_CLICKED_POST.invoker().mouseClicked(Minecraft.getInstance(), event.getButton(), event.getAction(), event.getModifiers());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventInputEvent(InputEvent.Key event) {
+ ClientRawInputEvent.KEY_PRESSED.invoker().keyPressed(Minecraft.getInstance(), event.getKey(), event.getScanCode(), event.getAction(), event.getModifiers());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(RegisterClientCommandsEvent event) {
+ ClientCommandRegistrationEvent.EVENT.invoker().register((CommandDispatcher)
+ (CommandDispatcher>) event.getDispatcher(), event.getBuildContext());
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ public static class ModBasedEventHandler {
+ // @SubscribeEvent(priority = EventPriority.HIGH)
+ // public static void eventTextureStitchEvent(TextureStitchEvent.Post event) {
+ // ClientTextureStitchEvent.POST.invoker().stitch(event.getAtlas());
+ // }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(FMLClientSetupEvent event) {
+ ClientLifecycleEvent.CLIENT_SETUP.invoker().stateChanged(Minecraft.getInstance());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(RegisterShadersEvent event) {
+ ClientReloadShadersEvent.EVENT.invoker().reload(event.getResourceProvider(), event::registerShader);
+ }
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java
new file mode 100644
index 00000000..f7f29b53
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java
@@ -0,0 +1,446 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import dev.architectury.event.CompoundEventResult;
+import dev.architectury.event.EventResult;
+import dev.architectury.event.events.common.PlayerEvent;
+import dev.architectury.event.events.common.*;
+import dev.architectury.utils.value.IntValue;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelAccessor;
+import net.neoforged.bus.api.Event;
+import net.neoforged.bus.api.EventPriority;
+import net.neoforged.bus.api.SubscribeEvent;
+import net.neoforged.fml.LogicalSide;
+import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
+import net.neoforged.neoforge.event.CommandEvent;
+import net.neoforged.neoforge.event.LootTableLoadEvent;
+import net.neoforged.neoforge.event.RegisterCommandsEvent;
+import net.neoforged.neoforge.event.ServerChatEvent;
+import net.neoforged.neoforge.event.TickEvent.LevelTickEvent;
+import net.neoforged.neoforge.event.TickEvent.Phase;
+import net.neoforged.neoforge.event.TickEvent.PlayerTickEvent;
+import net.neoforged.neoforge.event.TickEvent.ServerTickEvent;
+import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
+import net.neoforged.neoforge.event.entity.item.ItemTossEvent;
+import net.neoforged.neoforge.event.entity.living.AnimalTameEvent;
+import net.neoforged.neoforge.event.entity.living.LivingAttackEvent;
+import net.neoforged.neoforge.event.entity.living.LivingDeathEvent;
+import net.neoforged.neoforge.event.entity.living.MobSpawnEvent;
+import net.neoforged.neoforge.event.entity.player.*;
+import net.neoforged.neoforge.event.entity.player.PlayerEvent.*;
+import net.neoforged.neoforge.event.level.BlockEvent.BreakEvent;
+import net.neoforged.neoforge.event.level.BlockEvent.EntityPlaceEvent;
+import net.neoforged.neoforge.event.level.BlockEvent.FarmlandTrampleEvent;
+import net.neoforged.neoforge.event.level.ChunkDataEvent;
+import net.neoforged.neoforge.event.level.ExplosionEvent.Detonate;
+import net.neoforged.neoforge.event.level.ExplosionEvent.Start;
+import net.neoforged.neoforge.event.level.LevelEvent;
+import net.neoforged.neoforge.event.server.*;
+import net.neoforged.neoforge.server.ServerLifecycleHooks;
+
+public class EventHandlerImplCommon {
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerTickEvent event) {
+ if (event.phase == Phase.START)
+ TickEvent.SERVER_PRE.invoker().tick(ServerLifecycleHooks.getCurrentServer());
+ else if (event.phase == Phase.END)
+ TickEvent.SERVER_POST.invoker().tick(ServerLifecycleHooks.getCurrentServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(LevelTickEvent event) {
+ if (event.side == LogicalSide.SERVER) {
+ if (event.phase == Phase.START)
+ TickEvent.SERVER_LEVEL_PRE.invoker().tick((ServerLevel) event.level);
+ else if (event.phase == Phase.END)
+ TickEvent.SERVER_LEVEL_POST.invoker().tick((ServerLevel) event.level);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerStartingEvent event) {
+ LifecycleEvent.SERVER_STARTING.invoker().stateChanged(event.getServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerStartedEvent event) {
+ LifecycleEvent.SERVER_STARTED.invoker().stateChanged(event.getServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerStoppingEvent event) {
+ LifecycleEvent.SERVER_STOPPING.invoker().stateChanged(event.getServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerStoppedEvent event) {
+ LifecycleEvent.SERVER_STOPPED.invoker().stateChanged(event.getServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(RegisterCommandsEvent event) {
+ CommandRegistrationEvent.EVENT.invoker().register(event.getDispatcher(), event.getBuildContext(), event.getCommandSelection());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(PlayerLoggedInEvent event) {
+ PlayerEvent.PLAYER_JOIN.invoker().join((ServerPlayer) event.getEntity());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(PlayerLoggedOutEvent event) {
+ PlayerEvent.PLAYER_QUIT.invoker().quit((ServerPlayer) event.getEntity());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(PlayerRespawnEvent event) {
+ PlayerEvent.PLAYER_RESPAWN.invoker().respawn((ServerPlayer) event.getEntity(), event.isEndConquered());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(CommandEvent event) {
+ CommandPerformEvent performEvent = new CommandPerformEvent(event.getParseResults(), event.getException());
+ if (CommandPerformEvent.EVENT.invoker().act(performEvent).isFalse()) {
+ event.setCanceled(true);
+ }
+ event.setParseResults(performEvent.getResults());
+ event.setException(performEvent.getThrowable());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(PlayerTickEvent event) {
+ if (event.phase == Phase.START) {
+ TickEvent.PLAYER_PRE.invoker().tick(event.player);
+ } else if (event.phase == Phase.END) {
+ TickEvent.PLAYER_POST.invoker().tick(event.player);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerChatEvent event) {
+ class ChatComponentImpl implements ChatEvent.ChatComponent {
+ @Override
+ public Component get() {
+ return event.getMessage();
+ }
+
+ @Override
+ public void set(Component component) {
+ event.setMessage(component);
+ }
+ }
+ ChatEvent.DECORATE.invoker().decorate(event.getPlayer(), new ChatComponentImpl());
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public static void eventAfter(ServerChatEvent event) {
+ EventResult process = ChatEvent.RECEIVED.invoker().received(event.getPlayer(), event.getMessage());
+ if (process.isFalse())
+ event.setCanceled(true);
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventWorldEvent(LevelEvent.Load event) {
+ if (event.getLevel() instanceof ServerLevel) {
+ ServerLevel world = (ServerLevel) event.getLevel();
+ LifecycleEvent.SERVER_LEVEL_LOAD.invoker().act(world);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventWorldEvent(LevelEvent.Unload event) {
+ if (event.getLevel() instanceof ServerLevel) {
+ ServerLevel world = (ServerLevel) event.getLevel();
+ LifecycleEvent.SERVER_LEVEL_UNLOAD.invoker().act(world);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventWorldEvent(LevelEvent.Save event) {
+ if (event.getLevel() instanceof ServerLevel) {
+ ServerLevel world = (ServerLevel) event.getLevel();
+ LifecycleEvent.SERVER_LEVEL_SAVE.invoker().act(world);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(LivingDeathEvent event) {
+ if (EntityEvent.LIVING_DEATH.invoker().die(event.getEntity(), event.getSource()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(AdvancementEvent.AdvancementEarnEvent event) {
+ if (event.getEntity() instanceof ServerPlayer) {
+ PlayerEvent.PLAYER_ADVANCEMENT.invoker().award((ServerPlayer) event.getEntity(), event.getAdvancement());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerEvent(Clone event) {
+ if (event.getOriginal() instanceof ServerPlayer && event.getEntity() instanceof ServerPlayer) {
+ PlayerEvent.PLAYER_CLONE.invoker().clone((ServerPlayer) event.getOriginal(), (ServerPlayer) event.getEntity(), !event.isWasDeath());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventExplosionEvent(Start event) {
+ if (ExplosionEvent.PRE.invoker().explode(event.getLevel(), event.getExplosion()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventExplosionEvent(Detonate event) {
+ ExplosionEvent.DETONATE.invoker().explode(event.getLevel(), event.getExplosion(), event.getAffectedEntities());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(LivingAttackEvent event) {
+ if (EntityEvent.LIVING_HURT.invoker().hurt(event.getEntity(), event.getSource(), event.getAmount()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(EntityJoinLevelEvent event) {
+ if (EntityEvent.ADD.invoker().add(event.getEntity(), event.getLevel()).isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(FarmlandTrampleEvent event) {
+ if (event.getLevel() instanceof Level && InteractionEvent.FARMLAND_TRAMPLE.invoker().trample((Level) event.getLevel(), event.getPos(), event.getState(), event.getFallDistance(), event.getEntity()).value() != null) {
+ event.setCanceled(true);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(FillBucketEvent event) {
+ ItemStack oldItem = event.getEmptyBucket();
+ CompoundEventResult result = PlayerEvent.FILL_BUCKET.invoker().fill(event.getEntity(), event.getLevel(), oldItem, event.getTarget());
+ if (result.interruptsFurtherEvaluation()) {
+ event.setCanceled(true);
+ event.setFilledBucket(result.object());
+
+ if (result.value() != null) {
+ event.setResult(result.value() ? Event.Result.ALLOW : Event.Result.DENY);
+ }
+ }
+ }
+
+ // TODO: Hook ourselves when mixin is available
+ // @SubscribeEvent(priority = EventPriority.HIGH)
+ // public static void event(EnteringChunk event) {
+ // EntityEvent.ENTER_SECTION.invoker().enterChunk(event.getEntity(), event.getNewChunkX(), event.getNewChunkZ(), event.getOldChunkX(), event.getOldChunkZ());
+ // }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventLivingSpawnEvent(MobSpawnEvent.FinalizeSpawn event) {
+ EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(event.getEntity(), event.getLevel(), event.getX(), event.getY(), event.getZ(), event.getSpawnType(), event.getSpawner());
+ if (result.interruptsFurtherEvaluation()) {
+ if (!result.isEmpty()) {
+ event.setSpawnCancelled(result.value());
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(AnimalTameEvent event) {
+ EventResult result = EntityEvent.ANIMAL_TAME.invoker().tame(event.getAnimal(), event.getTamer());
+ event.setCanceled(result.isFalse());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ItemCraftedEvent event) {
+ PlayerEvent.CRAFT_ITEM.invoker().craft(event.getEntity(), event.getCrafting(), event.getInventory());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ItemSmeltedEvent event) {
+ PlayerEvent.SMELT_ITEM.invoker().smelt(event.getEntity(), event.getSmelting());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(EntityItemPickupEvent event) {
+ // note: this event is weird, cancel is equivalent to denying the pickup,
+ // and setting the result to ALLOW will pick it up without adding it to the player's inventory
+ var result = PlayerEvent.PICKUP_ITEM_PRE.invoker().canPickup(event.getEntity(), event.getItem(), event.getItem().getItem());
+ if (result.isFalse()) {
+ event.setCanceled(true);
+ } else if (result.isTrue()) {
+ event.setResult(Event.Result.ALLOW);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ItemPickupEvent event) {
+ PlayerEvent.PICKUP_ITEM_POST.invoker().pickup(event.getEntity(), event.getOriginalEntity(), event.getStack());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ItemTossEvent event) {
+ PlayerEvent.DROP_ITEM.invoker().drop(event.getPlayer(), event.getEntity());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerContainerEvent(PlayerContainerEvent.Open event) {
+ PlayerEvent.OPEN_MENU.invoker().open(event.getEntity(), event.getContainer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerContainerEvent(PlayerContainerEvent.Close event) {
+ PlayerEvent.CLOSE_MENU.invoker().close(event.getEntity(), event.getContainer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.RightClickItem event) {
+ CompoundEventResult result = InteractionEvent.RIGHT_CLICK_ITEM.invoker().click(event.getEntity(), event.getHand());
+ if (result.isPresent()) {
+ event.setCanceled(true);
+ event.setCancellationResult(result.result().asMinecraft());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.RightClickBlock event) {
+ EventResult result = InteractionEvent.RIGHT_CLICK_BLOCK.invoker().click(event.getEntity(), event.getHand(), event.getPos(), event.getFace());
+ if (result.isPresent()) {
+ event.setCanceled(true);
+ event.setCancellationResult(result.asMinecraft());
+ event.setUseBlock(Event.Result.DENY);
+ event.setUseItem(Event.Result.DENY);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.EntityInteract event) {
+ EventResult result = InteractionEvent.INTERACT_ENTITY.invoker().interact(event.getEntity(), event.getTarget(), event.getHand());
+ if (result.isPresent()) {
+ event.setCanceled(true);
+ event.setCancellationResult(result.asMinecraft());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventPlayerInteractEvent(PlayerInteractEvent.LeftClickBlock event) {
+ if (event.getAction() != PlayerInteractEvent.LeftClickBlock.Action.START) return;
+ EventResult result = InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(event.getEntity(), event.getHand(), event.getPos(), event.getFace());
+ if (result.isPresent()) {
+ event.setCanceled(true);
+ event.setUseBlock(result.value() ? Event.Result.ALLOW : Event.Result.DENY);
+ event.setUseItem(result.value() ? Event.Result.ALLOW : Event.Result.DENY);
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(BreakEvent event) {
+ if (event.getPlayer() instanceof ServerPlayer && event.getLevel() instanceof Level) {
+ EventResult result = BlockEvent.BREAK.invoker().breakBlock((Level) event.getLevel(), event.getPos(), event.getState(), (ServerPlayer) event.getPlayer(), new IntValue() {
+ @Override
+ public int getAsInt() {
+ return event.getExpToDrop();
+ }
+
+ @Override
+ public void accept(int value) {
+ event.setExpToDrop(value);
+ }
+ });
+ if (result.isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(EntityPlaceEvent event) {
+ if (event.getLevel() instanceof Level) {
+ EventResult result = BlockEvent.PLACE.invoker().placeBlock((Level) event.getLevel(), event.getPos(), event.getState(), event.getEntity());
+ if (result.isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(ServerAboutToStartEvent event) {
+ LifecycleEvent.SERVER_BEFORE_START.invoker().stateChanged(event.getServer());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(PlayerChangedDimensionEvent event) {
+ if (event.getEntity() instanceof ServerPlayer) {
+ PlayerEvent.CHANGE_DIMENSION.invoker().change((ServerPlayer) event.getEntity(), event.getFrom(), event.getTo());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventChunkDataEvent(ChunkDataEvent.Save event) {
+ if (event.getLevel() instanceof ServerLevel) {
+ ChunkEvent.SAVE_DATA.invoker().save(event.getChunk(), (ServerLevel) event.getLevel(), event.getData());
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void eventChunkDataEvent(ChunkDataEvent.Load event) {
+ LevelAccessor level = event.getChunk().getWorldForge();
+ if (!(level instanceof ServerLevel) && event instanceof LevelEventAttachment) {
+ level = ((LevelEventAttachment) event).architectury$getAttachedLevel();
+ }
+ ChunkEvent.LOAD_DATA.invoker().load(event.getChunk(), level instanceof ServerLevel ? (ServerLevel) level : null, event.getData());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(LootTableLoadEvent event) {
+ LootEvent.MODIFY_LOOT_TABLE.invoker().modifyLootTable(ResourceKey.create(Registries.LOOT_TABLE, event.getName()), new LootTableModificationContextImpl(event.getTable()), true);
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(AttackEntityEvent event) {
+ EventResult result = PlayerEvent.ATTACK_ENTITY.invoker().attack(event.getEntity(), event.getEntity().level(), event.getTarget(), event.getEntity().getUsedItemHand(), null);
+ if (result.isFalse()) {
+ event.setCanceled(true);
+ }
+ }
+
+ public interface LevelEventAttachment {
+ LevelAccessor architectury$getAttachedLevel();
+
+ void architectury$attachLevel(LevelAccessor level);
+ }
+
+ public static class ModBasedEventHandler {
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void event(FMLCommonSetupEvent event) {
+ LifecycleEvent.SETUP.invoker().run();
+ }
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplServer.java b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplServer.java
new file mode 100644
index 00000000..ac846a19
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/EventHandlerImplServer.java
@@ -0,0 +1,31 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.api.distmarker.OnlyIn;
+
+@OnlyIn(Dist.DEDICATED_SERVER)
+public class EventHandlerImplServer {
+ @OnlyIn(Dist.DEDICATED_SERVER)
+ public static class ModBasedEventHandler {
+
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java b/neoforge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java
new file mode 100644
index 00000000..540b10b5
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.event.forge;
+
+import dev.architectury.event.events.common.LootEvent;
+import net.minecraft.world.level.storage.loot.LootPool;
+import net.minecraft.world.level.storage.loot.LootTable;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+final class LootTableModificationContextImpl implements LootEvent.LootTableModificationContext {
+ private final LootTable table;
+ private final List pools;
+
+ LootTableModificationContextImpl(LootTable table) {
+ this.table = table;
+
+ // This field has the type changed to List by Forge
+ // Since this is rather unsafe, we are making sure 100% we are getting it
+ List pools = null;
+ try {
+ Field field = LootTable.class.getDeclaredField("pools");
+ field.setAccessible(true);
+ try {
+ pools = (List) field.get(table);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (NoSuchFieldException ignored3) {
+ for (Field field : LootTable.class.getDeclaredFields()) {
+ if (field.getType().equals(List.class)) {
+ // This is probably the field
+ field.setAccessible(true);
+ try {
+ pools = (List) field.get(table);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ if (pools == null) {
+ throw new RuntimeException("Unable to find pools field in LootTable!");
+ }
+ }
+
+ this.pools = pools;
+ }
+
+ @Override
+ public void addPool(LootPool.Builder pool) {
+ this.pools.add(pool.build());
+ }
+}
diff --git a/neoforge/src/main/java/dev/architectury/fluid/forge/FluidStackImpl.java b/neoforge/src/main/java/dev/architectury/fluid/forge/FluidStackImpl.java
new file mode 100644
index 00000000..bb3a1069
--- /dev/null
+++ b/neoforge/src/main/java/dev/architectury/fluid/forge/FluidStackImpl.java
@@ -0,0 +1,160 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package dev.architectury.fluid.forge;
+
+import com.mojang.serialization.Codec;
+import dev.architectury.hooks.fluid.forge.FluidStackHooksForge;
+import net.minecraft.core.Holder;
+import net.minecraft.core.component.DataComponentMap;
+import net.minecraft.core.component.DataComponentPatch;
+import net.minecraft.core.component.DataComponentType;
+import net.minecraft.core.component.PatchedDataComponentMap;
+import net.minecraft.network.RegistryFriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.world.level.material.Fluid;
+import net.neoforged.neoforge.fluids.FluidStack;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+import static dev.architectury.utils.Amount.toInt;
+
+@ApiStatus.Internal
+public enum FluidStackImpl implements dev.architectury.fluid.FluidStack.FluidStackAdapter {
+ INSTANCE;
+
+ static {
+ dev.architectury.fluid.FluidStack.init();
+ }
+
+ public static Function toValue;
+ public static Function