diff --git a/src/main/java/ru/bclib/BCLib.java b/src/main/java/ru/bclib/BCLib.java index d3174cae..15f4b841 100644 --- a/src/main/java/ru/bclib/BCLib.java +++ b/src/main/java/ru/bclib/BCLib.java @@ -6,6 +6,8 @@ import net.fabricmc.loader.api.FabricLoader; import net.minecraft.resources.ResourceLocation; import ru.bclib.api.TagAPI; import ru.bclib.api.WorldDataAPI; +import ru.bclib.api.dataexchange.DataExchangeAPI; +import ru.bclib.api.dataexchange.handler.HelloServer; import ru.bclib.config.Configs; import ru.bclib.recipes.CraftingRecipes; import ru.bclib.registry.BaseBlockEntities; @@ -26,6 +28,7 @@ public class BCLib implements ModInitializer { CraftingRecipes.init(); WorldDataAPI.registerModCache(MOD_ID); Configs.save(); + DataExchangeAPI.registerDescriptor(HelloServer.DESCRIPTOR); } public static boolean isDevEnvironment() { diff --git a/src/main/java/ru/bclib/api/dataexchange/Connector.java b/src/main/java/ru/bclib/api/dataexchange/Connector.java new file mode 100644 index 00000000..55a046a0 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/Connector.java @@ -0,0 +1,16 @@ +package ru.bclib.api.dataexchange; + +import java.util.Set; + +abstract class Connector { + protected final DataExchangeAPI api; + + Connector(DataExchangeAPI api) { + this.api = api; + } + public abstract boolean onClient(); + + protected Set getDescriptors(){ + return api.descriptors; + } +} diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java new file mode 100644 index 00000000..2e34765f --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java @@ -0,0 +1,66 @@ +package ru.bclib.api.dataexchange; + +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.PacketSender; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import ru.bclib.BCLib; + +@Environment(EnvType.CLIENT) +class ConnectorClientside extends Connector { + private Minecraft client; + ConnectorClientside(DataExchangeAPI api) { + super(api); + this.client = null; + } + + + @Override + public boolean onClient() { + return true; + } + + protected void onPlayInit(ClientPacketListener handler, Minecraft client){ + if (this.client!=null && this.client != client){ + BCLib.LOGGER.warning("Client changed!"); + } + this.client = client; + for(DataHandlerDescriptor desc : getDescriptors()){ + ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender)->{ + receiveFromServer(desc, _client, _handler, _buf, _responseSender); + }); + } + } + + void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client){ + for(DataHandlerDescriptor desc : getDescriptors()){ + if (desc.sendOnJoin){ + DataHandler h = desc.JOIN_INSTANCE.get(); + if (!h.getOriginatesOnServer()) { + h.sendToServer(client); + } + } + } + } + + void onPlayDisconnect(ClientPacketListener handler, Minecraft client){ + for(DataHandlerDescriptor desc : getDescriptors()) { + ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER); + } + } + + void receiveFromServer(DataHandlerDescriptor desc, Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender){ + DataHandler h = desc.INSTANCE.get(); + h.receiveFromServer(client, handler, buf, responseSender); + } + + void sendToServer(DataHandler h){ + if (client==null){ + throw new RuntimeException("[internal error] Client not initialized yet!"); + } + h.sendToServer(this.client); + } +} diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java new file mode 100644 index 00000000..b5d7e920 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java @@ -0,0 +1,63 @@ +package ru.bclib.api.dataexchange; + +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import ru.bclib.BCLib; + +class ConnectorServerside extends Connector { + private MinecraftServer server; + ConnectorServerside(DataExchangeAPI api) { + super(api); + server = null; + } + + @Override + public boolean onClient() { + return false; + } + + protected void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){ + if (this.server!=null && this.server != server){ + BCLib.LOGGER.warning("Server changed!"); + } + this.server = server; + for(DataHandlerDescriptor desc : getDescriptors()){ + ServerPlayNetworking.registerReceiver(handler, desc.IDENTIFIER, (_server, _player, _handler, _buf, _responseSender) -> { + receiveFromClient(desc, _server, _player, _handler, _buf, _responseSender); + }); + } + } + + void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server){ + for(DataHandlerDescriptor desc : getDescriptors()){ + if (desc.sendOnJoin){ + DataHandler h = desc.JOIN_INSTANCE.get(); + if (h.getOriginatesOnServer()) { + h.sendToClient(server, handler.player); + } + } + } + } + + void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server){ + for(DataHandlerDescriptor desc : getDescriptors()){ + ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER); + } + } + + void receiveFromClient(DataHandlerDescriptor desc, MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender){ + DataHandler h = desc.INSTANCE.get(); + h.receiveFromClient(server, player, handler, buf, responseSender); + } + + void sendToClient(DataHandler h){ + if (server==null){ + throw new RuntimeException("[internal error] Server not initialized yet!"); + } + h.sendToClient(this.server); + } +} diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java new file mode 100644 index 00000000..3796160a --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java @@ -0,0 +1,101 @@ +package ru.bclib.api.dataexchange; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.HashSet; +import java.util.Set; + +public class DataExchangeAPI { + private static DataExchangeAPI instance; + private ConnectorServerside server; + private ConnectorClientside client; + protected final Set descriptors; + + + private DataExchangeAPI(){ + descriptors = new HashSet<>(); + } + + static DataExchangeAPI getInstance(){ + if (instance==null){ + instance = new DataExchangeAPI(); + } + return instance; + } + + @Environment(EnvType.CLIENT) + private void initClientside(){ + if (client!=null) return; + client = new ConnectorClientside(this); + + ClientPlayConnectionEvents.INIT.register(client::onPlayInit); + ClientPlayConnectionEvents.JOIN.register(client::onPlayReady); + ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect); + } + + private void initServerSide(){ + if (server!=null) return; + server = new ConnectorServerside(this); + + ServerPlayConnectionEvents.INIT.register(server::onPlayInit); + ServerPlayConnectionEvents.JOIN.register(server::onPlayReady); + ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect); + } + + /** + * Add a new Descriptor for a DataHandler. + * @param desc The Descriptor you want to add. + */ + public static void registerDescriptor(DataHandlerDescriptor desc){ + DataExchangeAPI api = DataExchangeAPI.getInstance(); + api.descriptors.add(desc); + } + + + /** + * Initializes all datastructures that need to exist in the client component. + *

+ * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called + */ + @Environment(EnvType.CLIENT) + public static void prepareClientside(){ + DataExchangeAPI api = DataExchangeAPI.getInstance(); + api.initClientside(); + + } + + /** + * Initializes all datastructures that need to exist in the server component. + *

+ * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called + */ + public static void prepareServerside(){ + DataExchangeAPI api = DataExchangeAPI.getInstance(); + api.initServerSide(); + } + + + /** + * Sends the Handler. + *

+ * Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server + * to the client (if {@code true}) or the other way around. + *

+ * The method {@link DataHandler#serializeData(FriendlyByteBuf)} is called just before the data is sent. You should + * use this method to add the Data you need to the communication. + * @param h The Data that you want to send + */ + public static void send(DataHandler h){ + if (h.getOriginatesOnServer()){ + DataExchangeAPI.getInstance().server.sendToClient(h); + } else { + DataExchangeAPI.getInstance().client.sendToServer(h); + } + } + + +} diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java new file mode 100644 index 00000000..ece8b021 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java @@ -0,0 +1,107 @@ +package ru.bclib.api.dataexchange; + +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.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.jetbrains.annotations.NotNull; + +public abstract class DataHandler { + private final boolean originatesOnServer; + @NotNull + private final ResourceLocation identifier; + + protected DataHandler(ResourceLocation identifier, boolean originatesOnServer){ + this.originatesOnServer = originatesOnServer; + this.identifier = identifier; + } + + final public boolean getOriginatesOnServer(){ + return originatesOnServer; + } + + final public ResourceLocation getIdentifier(){ + return identifier; + } + + @Environment(EnvType.CLIENT) + void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender){ + deserializeFromIncomingData(buf, responseSender, false); + client.execute(() -> runOnClient(client)); + } + + void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender){ + deserializeFromIncomingData(buf, responseSender, true); + server.execute(() -> runOnServer(server)); + } + + protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient){ + } + + @Environment(EnvType.CLIENT) + protected void runOnClient(Minecraft client){ + + } + + protected void runOnServer(MinecraftServer server){ + + } + + protected void serializeData(FriendlyByteBuf buf) { + + } + + void sendToClient(MinecraftServer server){ + FriendlyByteBuf buf = PacketByteBufs.create(); + serializeData(buf); + + for (ServerPlayer player : PlayerLookup.all(server)) { + ServerPlayNetworking.send(player, this.identifier, buf); + } + } + + void sendToClient(MinecraftServer server, ServerPlayer player){ + FriendlyByteBuf buf = PacketByteBufs.create(); + serializeData(buf); + ServerPlayNetworking.send(player, this.identifier, buf); + } + + @Environment(EnvType.CLIENT) + void sendToServer(Minecraft client){ + FriendlyByteBuf buf = PacketByteBufs.create(); + serializeData(buf); + ClientPlayNetworking.send(identifier, buf); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DataHandler that = (DataHandler) o; + return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier); + } + + @Override + public int hashCode() { + int hash = identifier.hashCode(); + if (originatesOnServer) hash |= 0x80000000; + else hash &=0x7FFFFFFF; + + return hash; + } + + @Override + public String toString() { + return "DataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}'; + } +} diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java b/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java new file mode 100644 index 00000000..585a3725 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java @@ -0,0 +1,27 @@ +package ru.bclib.api.dataexchange; + +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Supplier; + +public class DataHandlerDescriptor { + public DataHandlerDescriptor(ResourceLocation identifier, Supplier instancer){ + this(identifier, instancer, instancer, false); + } + + public DataHandlerDescriptor(ResourceLocation identifier, Supplier instancer, boolean sendOnJoin){ + this(identifier, instancer, instancer, sendOnJoin); + } + public DataHandlerDescriptor(ResourceLocation identifier, Supplier receiv_instancer, Supplier join_instancer, boolean sendOnJoin){ + this.INSTANCE = receiv_instancer; + this.JOIN_INSTANCE = join_instancer; + this.IDENTIFIER = identifier; + + this.sendOnJoin = sendOnJoin; + } + + public final boolean sendOnJoin; + public final ResourceLocation IDENTIFIER; + public final Supplier INSTANCE; + public final Supplier JOIN_INSTANCE; +} diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java b/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java new file mode 100644 index 00000000..571de6d5 --- /dev/null +++ b/src/main/java/ru/bclib/api/dataexchange/handler/HelloServer.java @@ -0,0 +1,56 @@ +package ru.bclib.api.dataexchange.handler; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import ru.bclib.BCLib; +import ru.bclib.api.dataexchange.DataHandler; +import ru.bclib.api.dataexchange.DataHandlerDescriptor; +import ru.bclib.api.datafixer.DataFixerAPI; + +import java.util.Optional; + +public class HelloServer extends DataHandler { + public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, true); + + public HelloServer() { + super(DESCRIPTOR.IDENTIFIER, false); + } + + public static String getModVersion(String modID){ + Optional optional = FabricLoader.getInstance().getModContainer(modID); + if (optional.isPresent()) { + ModContainer modContainer = optional.get(); + return modContainer.getMetadata().getVersion().toString(); + } + return "0.0.0"; + } + + protected static String getBCLibVersion(){ + return getModVersion(BCLib.MOD_ID); + } + + @Override + protected void deserializeFromIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean fromClient) { + String bclibVersion = DataFixerAPI.getModVersion(buf.readInt()); + String localBclibVersion = getBCLibVersion(); + + BCLib.LOGGER.info("Hello Server received from BCLib. (server="+localBclibVersion+", client="+bclibVersion+")"); + } + + @Override + @Environment(EnvType.CLIENT) + protected void runOnClient(Minecraft client) { + + } + + @Override + protected void serializeData(FriendlyByteBuf buf) { + buf.writeInt(DataFixerAPI.getModVersion(getBCLibVersion())); + } +} diff --git a/src/main/java/ru/bclib/client/BCLibClient.java b/src/main/java/ru/bclib/client/BCLibClient.java index 7b338cfa..ff803130 100644 --- a/src/main/java/ru/bclib/client/BCLibClient.java +++ b/src/main/java/ru/bclib/client/BCLibClient.java @@ -3,6 +3,7 @@ package ru.bclib.client; import net.fabricmc.api.ClientModInitializer; import ru.bclib.api.ModIntegrationAPI; import ru.bclib.api.PostInitAPI; +import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.registry.BaseBlockEntityRenders; public class BCLibClient implements ClientModInitializer { @@ -10,6 +11,8 @@ public class BCLibClient implements ClientModInitializer { public void onInitializeClient() { ModIntegrationAPI.registerAll(); BaseBlockEntityRenders.register(); + DataExchangeAPI.prepareClientside(); + PostInitAPI.postInit(true); } } diff --git a/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java b/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java index 0fea24a6..203da109 100644 --- a/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java +++ b/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java @@ -1,12 +1,20 @@ package ru.bclib.mixin.common; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.datafixers.DataFixer; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.core.RegistryAccess.RegistryHolder; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerResources; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.players.GameProfileCache; import net.minecraft.world.level.Level; import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess; import net.minecraft.world.level.storage.WorldData; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -16,8 +24,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import ru.bclib.api.BiomeAPI; +import ru.bclib.api.dataexchange.DataExchangeAPI; import ru.bclib.recipes.BCLRecipeManager; +import java.net.Proxy; import java.util.Collection; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -34,7 +44,10 @@ public class MinecraftServerMixin { @Final @Shadow protected WorldData worldData; - + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onServerInit(Thread thread, RegistryHolder registryHolder, LevelStorageAccess levelStorageAccess, WorldData worldData, PackRepository packRepository, Proxy proxy, DataFixer dataFixer, ServerResources serverResources, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, GameProfileCache gameProfileCache, ChunkProgressListenerFactory chunkProgressListenerFactory, CallbackInfo ci){ + DataExchangeAPI.prepareServerside(); + } @Inject(method="convertFromRegionFormatIfNeeded", at = @At("HEAD")) private static void bclib_applyPatches(LevelStorageSource.LevelStorageAccess session, CallbackInfo ci){ diff --git a/src/main/java/ru/bclib/server/BCLibServer.java b/src/main/java/ru/bclib/server/BCLibServer.java index e1ba1107..d4011c97 100644 --- a/src/main/java/ru/bclib/server/BCLibServer.java +++ b/src/main/java/ru/bclib/server/BCLibServer.java @@ -3,11 +3,14 @@ package ru.bclib.server; import net.fabricmc.api.DedicatedServerModInitializer; import ru.bclib.api.ModIntegrationAPI; import ru.bclib.api.PostInitAPI; +import ru.bclib.api.dataexchange.*; public class BCLibServer implements DedicatedServerModInitializer { @Override public void onInitializeServer() { ModIntegrationAPI.registerAll(); + DataExchangeAPI.prepareServerside(); + PostInitAPI.postInit(false); } }