9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-19 14:59:25 +00:00

add leaves protocols, load config a little bit earlier

This commit is contained in:
NONPLAYT
2025-07-11 01:25:21 +03:00
parent 6dca0972a0
commit 1d451b4441
101 changed files with 6187 additions and 19 deletions

View File

@@ -12,6 +12,8 @@ public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities lineOfS
public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities nearbyEntities public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities nearbyEntities
public net.minecraft.world.entity.ai.sensing.Sensor scanRate public net.minecraft.world.entity.ai.sensing.Sensor scanRate
public net.minecraft.world.entity.ai.sensing.Sensor timeToTick public net.minecraft.world.entity.ai.sensing.Sensor timeToTick
public net.minecraft.world.entity.animal.armadillo.Armadillo scuteTime
public net.minecraft.world.entity.animal.frog.Tadpole getTicksLeftUntilAdult()I
public net.minecraft.world.level.chunk.PaletteResize public net.minecraft.world.level.chunk.PaletteResize
public net.minecraft.world.level.entity.EntityTickList entities public net.minecraft.world.level.entity.EntityTickList entities
public net.minecraft.world.level.levelgen.DensityFunctions$BlendAlpha public net.minecraft.world.level.levelgen.DensityFunctions$BlendAlpha
@@ -41,4 +43,10 @@ public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedLo
public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator
public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates
public net.minecraft.world.level.pathfinder.SwimNodeEvaluator allowBreaching public net.minecraft.world.level.pathfinder.SwimNodeEvaluator allowBreaching
public net.minecraft.world.level.storage.loot.LootPool entries
public net.minecraft.world.level.storage.loot.LootTable pools
public net.minecraft.world.level.storage.loot.entries.CompositeEntryBase children
public net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer conditions
public net.minecraft.world.level.storage.loot.entries.NestedLootTable contents
public net.minecraft.world.level.storage.loot.predicates.CompositeLootItemCondition terms
public-f ca.spottedleaf.moonrise.paper.PaperHooks public-f ca.spottedleaf.moonrise.paper.PaperHooks

View File

@@ -5,17 +5,23 @@ Subject: [PATCH] Configuration
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 670553243d26e2faab8a21f099a846d4d1df7927..329aeeafd51aee4da289b70ff68cdfe5401cc91a 100644 index 670553243d26e2faab8a21f099a846d4d1df7927..83bd88eb9944481642f912c2d4862dfecdec2246 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -193,6 +193,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -162,6 +162,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.setLocalIp(properties.serverIp);
}
+ org.bxteam.divinemc.config.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings")); // DivineMC - Configuration
+
// Spigot start
this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
@@ -193,6 +195,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
} }
org.purpurmc.purpur.PurpurConfig.registerCommands(); org.purpurmc.purpur.PurpurConfig.registerCommands();
// Purpur end - Purpur config files // Purpur end - Purpur config files
+ // DivineMC start - Configuration + org.bxteam.divinemc.command.DivineCommands.registerCommands(this); // DivineMC - Configuration
+ org.bxteam.divinemc.config.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings"));
+ org.bxteam.divinemc.command.DivineCommands.registerCommands(this);
+ // DivineMC end - Configuration
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
this.setPvpAllowed(properties.pvp); this.setPvpAllowed(properties.pvp);

View File

@@ -1067,10 +1067,10 @@ index b10cb4a73df58a5fe64e88868733ba41616f59e4..9f9cbe6056f8a4eeca64c40872d7403b
+ // DivineMC end - Completely remove Mojang profiler + // DivineMC end - Completely remove Mojang profiler
} }
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 329aeeafd51aee4da289b70ff68cdfe5401cc91a..653988aed936761385f245c520cc9521351664bf 100644 index 83bd88eb9944481642f912c2d4862dfecdec2246..06657a0fbf65dd095519d706dd5a8e1d8b6b381a 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -790,12 +790,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -789,12 +789,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
return this.settings.getProperties().serverResourcePackInfo; return this.settings.getProperties().serverResourcePackInfo;
} }
@@ -4493,7 +4493,7 @@ index cf6ff7b7b4a007d7ff4b3c5a25d4f5a36422c683..c5275d6069a491c3c2b2de175b76fb87
} }
diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
index 0da5c51c4830cf1826261f4d8877303b34c6cb87..6fbeaff7178a21338920d6738767033260b7a726 100644 index e4578193f58417c7ef2776bb3d831ba55c553aec..2a7a078e05e16e73e43a24e108d207bce2e876bb 100644
--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java --- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java
+++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
@@ -23,8 +23,6 @@ import net.minecraft.util.ByIdMap; @@ -23,8 +23,6 @@ import net.minecraft.util.ByIdMap;
@@ -4604,7 +4604,7 @@ index 1d5079602e7ae1042e2bb92209dded4007f703da..c6e4966d3e4fdb7c91577fc1693fb669
} }
diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java
index 6932e85b3db0205f9a69d9ef965a934f100e6bcf..c0e12a6e5dd2b7e12e4cc40f6795228de6b470cc 100644 index 3bb197054f5197c0b8c4e2d4714d695255d5ecfa..f85626b690b02908fac3979d277b293ec48aa451 100644
--- a/net/minecraft/world/entity/animal/frog/Tadpole.java --- a/net/minecraft/world/entity/animal/frog/Tadpole.java
+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
@@ -12,8 +12,6 @@ import net.minecraft.server.level.ServerLevel; @@ -12,8 +12,6 @@ import net.minecraft.server.level.ServerLevel;

View File

@@ -7,11 +7,11 @@ Original license: GPL v3
Original project: https://github.com/pufferfish-gg/Pufferfish Original project: https://github.com/pufferfish-gg/Pufferfish
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 653988aed936761385f245c520cc9521351664bf..f963be0d96a54a9077d2d66e9abe73ba8484d653 100644 index 06657a0fbf65dd095519d706dd5a8e1d8b6b381a..a947fc31f6261531b6f28e5af577e74ca84f2132 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -199,6 +199,26 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -198,6 +198,26 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
// DivineMC end - Configuration org.bxteam.divinemc.command.DivineCommands.registerCommands(this); // DivineMC - Configuration
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
+ // DivineMC start - Pufferfish SIMD + // DivineMC start - Pufferfish SIMD

View File

@@ -206,10 +206,10 @@ index a491be4250de3199c3e1aa9e5482b568692bd2f5..c88826db76c28c536e6c36c5592d69c1
private static final String PREFIX = "data:image/png;base64,"; private static final String PREFIX = "data:image/png;base64,";
public static final Codec<ServerStatus.Favicon> CODEC = Codec.STRING.comapFlatMap(string -> { public static final Codec<ServerStatus.Favicon> CODEC = Codec.STRING.comapFlatMap(string -> {
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index f963be0d96a54a9077d2d66e9abe73ba8484d653..efe1e50af314c4b8f6b30c7eb0a8dd14aec327a9 100644 index a947fc31f6261531b6f28e5af577e74ca84f2132..021e1cb762d23ebe885a3f190ba2431e1db99bb8 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -624,6 +624,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -623,6 +623,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@Override @Override
public boolean enforceSecureProfile() { public boolean enforceSecureProfile() {

View File

@@ -333,10 +333,10 @@ index 10e5469df1800bcdfb3f8cb4045ee25a4bafc58c..8efed0ffdc906b6c1ba054831e481f53
} }
} else if (this.visible.remove(advancementHolder)) { } else if (this.visible.remove(advancementHolder)) {
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index efe1e50af314c4b8f6b30c7eb0a8dd14aec327a9..41900b602d763fdd1f393e29ef6c54ae2694d7bd 100644 index 021e1cb762d23ebe885a3f190ba2431e1db99bb8..8820d1789192247c52b4d821abf2dd23c0bf1b62 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -219,6 +219,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -218,6 +218,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
} }
// DivineMC end - Pufferfish SIMD // DivineMC end - Pufferfish SIMD

View File

@@ -5,10 +5,10 @@ Subject: [PATCH] Disable offline warn if using proxy
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 41900b602d763fdd1f393e29ef6c54ae2694d7bd..0e66aa733dd9b0c23ef01bb38243a0cab13d9ecf 100644 index 8820d1789192247c52b4d821abf2dd23c0bf1b62..5a2b9632a1e46b512a1379923765c1b8a28250b9 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java --- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -306,7 +306,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -305,7 +305,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord";
String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/";
// Paper end - Add Velocity IP Forwarding Support // Paper end - Add Velocity IP Forwarding Support

View File

@@ -0,0 +1,146 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Thu, 10 Jul 2025 22:11:47 +0300
Subject: [PATCH] Leaves: Protocol Core
Original project: https://github.com/LeavesMC/Leaves
Original license: GPLv3
diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..c3be4c2fd4a544967322a45d3b8c0fe78a0684a5 100644
--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
+++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
@@ -40,13 +40,22 @@ public interface CustomPacketPayload {
@Override
public void encode(B buffer, CustomPacketPayload value) {
+ // DivineMC start - Leaves Protocol Core
+ if (value instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload payload) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.encode(buffer, payload);
+ return;
+ }
+ // DivineMC end - Leaves Protocol Core
this.writeCap(buffer, value.type(), value);
}
@Override
public CustomPacketPayload decode(B buffer) {
ResourceLocation resourceLocation = buffer.readResourceLocation();
- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(buffer);
+ // DivineMC start - Leaves Protocol Core
+ var payload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, buffer);
+ return java.util.Objects.requireNonNullElseGet(payload, () -> this.findCodec(resourceLocation).decode(buffer));
+ // DivineMC end - Leaves Protocol Core
}
};
}
diff --git a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
index 62b9d9486c15a1ec6527f786df4e9fc483390bcb..36d8b93182cc44e3bea245800ea9e2719333ac65 100644
--- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
+++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
@@ -4,12 +4,12 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
-public record DiscardedPayload(ResourceLocation id, byte[] data) implements CustomPacketPayload { // Paper - store data
+public record DiscardedPayload(ResourceLocation id, byte @org.jetbrains.annotations.Nullable [] data) implements CustomPacketPayload { // Paper - store data // DivineMC - Leaves Protocol Core
public static <T extends FriendlyByteBuf> StreamCodec<T, DiscardedPayload> codec(ResourceLocation id, int maxSize) {
return CustomPacketPayload.codec((value, output) -> {
// Paper start
// Always write data
- output.writeBytes(value.data);
+ if (value.data != null) output.writeBytes(value.data); // DivineMC - Leaves Protocol Core
}, buffer -> {
int i = buffer.readableBytes();
if (i >= 0 && i <= maxSize) {
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index b31a4edee0616a63026f7a4335205f2d99d2f641..0072f3f07b1962adc1766930bb9a2f709cb76e6e 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1788,6 +1788,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
GameTestTicker.SINGLETON.tick();
}
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleTick(tickCount); // DivineMC - Leaves Protocol Core
+
for (int i = 0; i < this.tickables.size(); i++) {
this.tickables.get(i).run();
}
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 52f3c374c91f877b51b243b46cd05c5e2fc0face..d383532219bccb706bebbd3ef2923c70553d1a46 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -432,6 +432,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
private boolean ramBar = false; // Purpur - Implement rambar commands
public boolean smoothWorldTeleport; // DivineMC - Smooth teleport API
public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking
+ public net.minecraft.network.Connection internalConnection; // DivineMC - Leaves Protocol Core
// Paper start - rewrite chunk system
private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index a7c4fad2b1cb0cbac742a18d37d688bb2663944e..b94243d293e805743453adf7b4fc8d852184f460 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -230,6 +230,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII);
if (register) {
bridge.addChannel(channel);
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, bridge); // DivineMC - Leaves Protocol Core
} else {
bridge.removeChannel(channel);
}
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index bc738535f67d789e9d240b245e9247e026b3c751..9d771e7fba94c09df602a249f58a9caf1d339bcf 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3734,6 +3734,17 @@ public class ServerGamePacketListenerImpl
@Override
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+ // DivineMC start - Leaves Protocol Core
+ if (packet.payload() instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesPayload) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(player, leavesPayload);
+ return;
+ }
+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.DiscardedPayload(net.minecraft.resources.ResourceLocation id, byte[] data)) {
+ if (org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleBytebuf(player, id, io.netty.buffer.Unpooled.wrappedBuffer(data))) {
+ return;
+ }
+ }
+ // DivineMC end - Leaves Protocol Core
super.handleCustomPayload(packet); // Paper
}
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 08e337ae96c444da2e50240c808c76a548cd4c12..f65599e41ca77756cc9bfb87c4a86606eed127cf 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -343,6 +343,11 @@ public abstract class PlayerList {
return;
}
+ // DivineMC start - Leaves Protocol Core
+ if (player.internalConnection == null) player.internalConnection = connection;
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player);
+ // DivineMC end - Leaves Protocol Core
+
final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
@@ -516,6 +521,7 @@ public abstract class PlayerList {
return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
}
public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(player); // DivineMC - Leaves Protocol Core
// Paper end - Fix kick event leave message not being sent
org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar
ServerLevel serverLevel = player.level();
@@ -1460,6 +1466,7 @@ public abstract class PlayerList {
serverPlayer.connection.send(clientboundUpdateRecipesPacket);
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
}
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleDataPackReload(); // DivineMC - Leaves Protocol Core
}
public boolean isAllowCommandsForAllPlayers() {

View File

@@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Thu, 10 Jul 2025 22:15:39 +0300
Subject: [PATCH] Leaves: Xaero's Map Protocol
Original project: https://github.com/LeavesMC/Leaves
Original license: GPLv3
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index f65599e41ca77756cc9bfb87c4a86606eed127cf..753f921042c8fcfc0eb6c2dca7f80751df4909e3 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1172,6 +1172,7 @@ public abstract class PlayerList {
player.connection.send(new ClientboundInitializeBorderPacket(worldBorder));
player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
+ org.leavesmc.leaves.protocol.XaeroMapProtocol.onSendWorldInfo(player); // DivineMC - Leaves: Xaero's Map Protocol
if (level.isRaining()) {
// CraftBukkit start - handle player weather
// player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));

View File

@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Thu, 10 Jul 2025 22:17:00 +0300
Subject: [PATCH] Leaves: Syncmatica Protocol
Original project: https://github.com/LeavesMC/Leaves
Original license: GPLv3
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 9d771e7fba94c09df602a249f58a9caf1d339bcf..db85f8f905425316f893d0adccdeef53517faba8 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -313,6 +313,7 @@ public class ServerGamePacketListenerImpl
private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length
private final io.papermc.paper.event.packet.ClientTickEndEvent tickEndEvent; // Paper - add client tick end event
public final io.papermc.paper.connection.PaperPlayerGameConnection playerGameConnection; // Paper
+ public final org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // DivineMC - Leaves: Syncmatica Protocol
public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
super(server, connection, cookie);
@@ -324,6 +325,7 @@ public class ServerGamePacketListenerImpl
this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat
this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event
this.playerGameConnection = new io.papermc.paper.connection.PaperPlayerGameConnection(this); // Paper
+ this.exchangeTarget = new org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget(this); // DivineMC - Leaves: Syncmatica Protocol
}
// Paper start - configuration phase API

View File

@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Thu, 10 Jul 2025 22:12:19 +0300
Subject: [PATCH] Leaves: Protocol Core
Original project: https://github.com/LeavesMC/Leaves
Original license: GPLv3
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index bf549edfc876f1d831b550864b3f54278bcdd20c..bf53f7343f5c8dd6f3bd995cfcebe7b88472659b 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -510,6 +510,7 @@ public final class CraftServer implements Server {
this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // DivineMC - Leaves Protocol Core
}
public boolean getCommandBlockOverride(String command) {
@@ -1099,6 +1100,7 @@ public final class CraftServer implements Server {
org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // DivineMC - Leaves Protocol Core
int pollCount = 0;

View File

@@ -26,6 +26,7 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Random;
@SuppressWarnings({"unused", "SameParameterValue"}) @SuppressWarnings({"unused", "SameParameterValue"})
public class DivineConfig { public class DivineConfig {
@@ -688,9 +689,20 @@ public class DivineConfig {
public static boolean noChatReportsDemandOnClient = false; public static boolean noChatReportsDemandOnClient = false;
public static String noChatReportsDisconnectDemandOnClientMessage = "You do not have No Chat Reports, and this server is configured to require it on client!"; public static String noChatReportsDisconnectDemandOnClientMessage = "You do not have No Chat Reports, and this server is configured to require it on client!";
// Protocols
public static boolean protocolsAppleSkinEnabled = false;
public static int protocolsAppleSkinSyncTickInterval = 20;
public static boolean protocolsJadeEnabled = false;
public static boolean protocolsMapsXaeroMapEnabled = false;
public static int protocolsMapsXaeroMapServerId = new Random().nextInt();
public static boolean protocolsSyncMaticaEnabled = false;
public static boolean protocolsSyncMaticaQuota = false;
public static int protocolsSyncMaticaQuotaLimit = 40000000;
public static void load() { public static void load() {
networkSettings(); networkSettings();
noChatReports(); noChatReports();
protocols();
} }
private static void networkSettings() { private static void networkSettings() {
@@ -718,6 +730,32 @@ public class DivineConfig {
noChatReportsDisconnectDemandOnClientMessage = getString(ConfigCategory.NETWORK.key("no-chat-reports.disconnect-demand-on-client-message"), noChatReportsDisconnectDemandOnClientMessage, noChatReportsDisconnectDemandOnClientMessage = getString(ConfigCategory.NETWORK.key("no-chat-reports.disconnect-demand-on-client-message"), noChatReportsDisconnectDemandOnClientMessage,
"Message to send to the client when they are disconnected for not having No Chat Reports"); "Message to send to the client when they are disconnected for not having No Chat Reports");
} }
private static void protocols() {
// AppleSkin
protocolsAppleSkinEnabled = getBoolean(ConfigCategory.NETWORK.key("protocols.appleskin.appleskin-enable"), protocolsAppleSkinEnabled,
"Enables AppleSkin protocol support");
protocolsAppleSkinSyncTickInterval = getInt(ConfigCategory.NETWORK.key("protocols.appleskin.sync-tick-interval"), protocolsAppleSkinSyncTickInterval,
"Sync tick interval for AppleSkin protocol");
// Jade
protocolsJadeEnabled = getBoolean(ConfigCategory.NETWORK.key("protocols.jade.jade-enable"), protocolsJadeEnabled,
"Enables Jade protocol support");
// Xaero's Map
protocolsMapsXaeroMapEnabled = getBoolean(ConfigCategory.NETWORK.key("protocols.xaeromap.xaeromap-enable"), protocolsMapsXaeroMapEnabled,
"Enables Xaero's Map protocol support");
protocolsMapsXaeroMapServerId = getInt(ConfigCategory.NETWORK.key("protocols.xaeromap.xaero-map-server-id"), protocolsMapsXaeroMapServerId,
"Server ID for Xaero's Map protocol");
// Syncmatica
protocolsSyncMaticaEnabled = getBoolean(ConfigCategory.NETWORK.key("protocols.syncmatica.syncmatica-enable"), protocolsSyncMaticaEnabled,
"Enables SyncMatica protocol support");
protocolsSyncMaticaQuota = getBoolean(ConfigCategory.NETWORK.key("protocols.syncmatica.quota"), protocolsSyncMaticaQuota,
"Enables quota system for SyncMatica");
protocolsSyncMaticaQuotaLimit = getInt(ConfigCategory.NETWORK.key("protocols.syncmatica.quota-limit"), protocolsSyncMaticaQuotaLimit,
"Quota limit for SyncMatica protocol");
}
} }
private static void checkExperimentalFeatures() { private static void checkExperimentalFeatures() {

View File

@@ -0,0 +1,24 @@
package org.leavesmc.leaves;
import org.bukkit.Bukkit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LeavesLogger extends Logger {
public static final LeavesLogger LOGGER = new LeavesLogger();
private LeavesLogger() {
super("Leaves", null);
setParent(Bukkit.getLogger());
setLevel(Level.ALL);
}
public void severe(String msg, Exception exception) {
this.log(Level.SEVERE, msg, exception);
}
public void warning(String msg, Exception exception) {
this.log(Level.WARNING, msg, exception);
}
}

View File

@@ -0,0 +1,122 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.level.GameRules;
import org.bxteam.divinemc.config.DivineConfig;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@LeavesProtocol.Register(namespace = "appleskin")
public class AppleSkinProtocol implements LeavesProtocol {
public static final String PROTOCOL_ID = "appleskin";
private static final ResourceLocation SATURATION_KEY = id("saturation");
private static final ResourceLocation EXHAUSTION_KEY = id("exhaustion");
private static final ResourceLocation NATURAL_REGENERATION_KEY = id("natural_regeneration");
private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F;
private static final Map<ServerPlayer, Float> previousSaturationLevels = new HashMap<>();
private static final Map<ServerPlayer, Float> previousExhaustionLevels = new HashMap<>();
private static final Map<ServerPlayer, Boolean> previousNaturalRegeneration = new HashMap<>();
private static final Map<ServerPlayer, Set<String>> subscribedChannels = new HashMap<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.PlayerJoin
public static void onPlayerLoggedIn(@NotNull ServerPlayer player) {
resetPlayerData(player);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
subscribedChannels.remove(player);
resetPlayerData(player);
}
@ProtocolHandler.MinecraftRegister(onlyNamespace = true)
public static void onPlayerSubscribed(@NotNull ServerPlayer player, ResourceLocation id) {
subscribedChannels.computeIfAbsent(player, k -> new HashSet<>()).add(id.getPath());
}
@ProtocolHandler.Ticker
public static void tick() {
for (Map.Entry<ServerPlayer, Set<String>> entry : subscribedChannels.entrySet()) {
ServerPlayer player = entry.getKey();
FoodData data = player.getFoodData();
for (String channel : entry.getValue()) {
switch (channel) {
case "saturation" -> {
float saturation = data.getSaturationLevel();
Float previousSaturation = previousSaturationLevels.get(player);
if (previousSaturation == null || saturation != previousSaturation) {
ProtocolUtils.sendBytebufPacket(player, SATURATION_KEY, buf -> buf.writeFloat(saturation));
previousSaturationLevels.put(player, saturation);
}
}
case "exhaustion" -> {
float exhaustion = data.exhaustionLevel;
Float previousExhaustion = previousExhaustionLevels.get(player);
if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= MINIMUM_EXHAUSTION_CHANGE_THRESHOLD) {
ProtocolUtils.sendBytebufPacket(player, EXHAUSTION_KEY, buf -> buf.writeFloat(exhaustion));
previousExhaustionLevels.put(player, exhaustion);
}
}
case "natural_regeneration" -> {
boolean regeneration = player.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
Boolean previousRegeneration = previousNaturalRegeneration.get(player);
if (previousRegeneration == null || regeneration != previousRegeneration) {
ProtocolUtils.sendBytebufPacket(player, NATURAL_REGENERATION_KEY, buf -> buf.writeBoolean(regeneration));
previousNaturalRegeneration.put(player, regeneration);
}
}
}
}
}
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
disableAllPlayer();
}
public static void disableAllPlayer() {
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
onPlayerLoggedOut(player);
}
}
private static void resetPlayerData(@NotNull ServerPlayer player) {
previousExhaustionLevels.remove(player);
previousSaturationLevels.remove(player);
previousNaturalRegeneration.remove(player);
}
@Override
public int tickerInterval(String tickerID) {
return DivineConfig.NetworkCategory.protocolsAppleSkinSyncTickInterval;
}
@Override
public boolean isActive() {
return DivineConfig.NetworkCategory.protocolsAppleSkinEnabled;
}
}

View File

@@ -0,0 +1,46 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.bxteam.divinemc.config.DivineConfig;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
@LeavesProtocol.Register(namespace = "xaerominimap_or_xaeroworldmap_i_dont_care")
public class XaeroMapProtocol implements LeavesProtocol {
public static final String PROTOCOL_ID_MINI = "xaerominimap";
public static final String PROTOCOL_ID_WORLD = "xaeroworldmap";
private static final ResourceLocation MINIMAP_KEY = idMini("main");
private static final ResourceLocation WORLDMAP_KEY = idWorld("main");
@Contract("_ -> new")
public static ResourceLocation idMini(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID_MINI, path);
}
@Contract("_ -> new")
public static ResourceLocation idWorld(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID_WORLD, path);
}
public static void onSendWorldInfo(@NotNull ServerPlayer player) {
if (DivineConfig.NetworkCategory.protocolsMapsXaeroMapEnabled) {
ProtocolUtils.sendBytebufPacket(player, MINIMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(DivineConfig.NetworkCategory.protocolsMapsXaeroMapServerId);
});
ProtocolUtils.sendBytebufPacket(player, WORLDMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(DivineConfig.NetworkCategory.protocolsMapsXaeroMapServerId);
});
}
}
@Override
public boolean isActive() {
return DivineConfig.NetworkCategory.protocolsMapsXaeroMapEnabled;
}
}

View File

@@ -0,0 +1,29 @@
package org.leavesmc.leaves.protocol.core;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public interface LeavesCustomPayload extends CustomPacketPayload {
Type<? extends CustomPacketPayload> LEAVES_TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("leaves", "custom_payload"));
@Override
default @NotNull Type<? extends CustomPacketPayload> type() {
return LEAVES_TYPE;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface ID {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Codec {
}
}

View File

@@ -0,0 +1,20 @@
package org.leavesmc.leaves.protocol.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public interface LeavesProtocol {
boolean isActive();
default int tickerInterval(String tickerID) {
return 1;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Register {
String namespace();
}
}

View File

@@ -0,0 +1,455 @@
package org.leavesmc.leaves.protocol.core;
import io.netty.buffer.ByteBuf;
import io.papermc.paper.connection.PluginMessageBridgeImpl;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.core.invoker.BytebufReceiverInvokerHolder;
import org.leavesmc.leaves.protocol.core.invoker.EmptyInvokerHolder;
import org.leavesmc.leaves.protocol.core.invoker.InitInvokerHolder;
import org.leavesmc.leaves.protocol.core.invoker.MinecraftRegisterInvokerHolder;
import org.leavesmc.leaves.protocol.core.invoker.PayloadReceiverInvokerHolder;
import org.leavesmc.leaves.protocol.core.invoker.PlayerInvokerHolder;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class LeavesProtocolManager {
private static final LeavesLogger LOGGER = LeavesLogger.LOGGER;
private static final Map<Class<? extends LeavesCustomPayload>, PayloadReceiverInvokerHolder> PAYLOAD_RECEIVERS = new HashMap<>();
private static final Map<Class<? extends LeavesCustomPayload>, ResourceLocation> IDS = new HashMap<>();
private static final Map<Class<? extends LeavesCustomPayload>, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> CODECS = new HashMap<>();
private static final Map<ResourceLocation, StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>> ID2CODEC = new HashMap<>();
private static final Map<String, BytebufReceiverInvokerHolder> STRICT_BYTEBUF_RECEIVERS = new HashMap<>();
private static final Map<String, BytebufReceiverInvokerHolder> NAMESPACED_BYTEBUF_RECEIVERS = new HashMap<>();
private static final List<BytebufReceiverInvokerHolder> GENERIC_BYTEBUF_RECEIVERS = new ArrayList<>();
private static final Map<String, MinecraftRegisterInvokerHolder> STRICT_MINECRAFT_REGISTER = new HashMap<>();
private static final Map<String, MinecraftRegisterInvokerHolder> NAMESPACED_MINECRAFT_REGISTER = new HashMap<>();
private static final List<MinecraftRegisterInvokerHolder> WILD_MINECRAFT_REGISTER = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.Ticker>> TICKERS = new ArrayList<>();
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerJoin>> PLAYER_JOIN = new ArrayList<>();
private static final List<PlayerInvokerHolder<ProtocolHandler.PlayerLeave>> PLAYER_LEAVE = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadServer>> RELOAD_SERVER = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadDataPack>> RELOAD_DATAPACK = new ArrayList<>();
@SuppressWarnings("unchecked")
public static void init() {
for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
if (LeavesCustomPayload.class.isAssignableFrom(clazz) && !clazz.equals(LeavesCustomPayload.class)) {
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (!Modifier.isStatic(field.getModifiers())) {
continue;
}
try {
final LeavesCustomPayload.ID id = field.getAnnotation(LeavesCustomPayload.ID.class);
if (id != null && field.getType().equals(ResourceLocation.class)) {
IDS.put((Class<? extends LeavesCustomPayload>) clazz, (ResourceLocation) field.get(null));
}
final LeavesCustomPayload.Codec codec = field.getAnnotation(LeavesCustomPayload.Codec.class);
if (codec != null && field.getType().equals(StreamCodec.class)) {
CODECS.put((Class<? extends LeavesCustomPayload>) clazz, (StreamCodec<? super RegistryFriendlyByteBuf, LeavesCustomPayload>) field.get(null));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
continue;
}
final LeavesProtocol.Register register = clazz.getAnnotation(LeavesProtocol.Register.class);
if (register == null) {
continue;
}
LeavesProtocol protocol;
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
protocol = (LeavesProtocol) constructor.newInstance();
} catch (Throwable throwable) {
LOGGER.severe("Failed to load class " + clazz.getName() + ". " + throwable);
return;
}
boolean active = true;
try {
Method isActiveMethod = clazz.getDeclaredMethod("isActive");
isActiveMethod.setAccessible(true);
active = (Boolean) isActiveMethod.invoke(protocol);
} catch (Throwable e) {
LOGGER.warning("Failed to check isActive for " + clazz.getName() + ": " + e);
continue;
}
for (final Method method : clazz.getDeclaredMethods()) {
if (method.isBridge() || method.isSynthetic()) {
continue;
}
method.setAccessible(true);
final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class);
if (init != null) {
InitInvokerHolder holder = new InitInvokerHolder(protocol, method, init);
try {
holder.invoke();
} catch (RuntimeException exception) {
LOGGER.severe("Failed to invoke init method " + method.getName() + " in " + clazz.getName() + ", " + exception.getCause() + ": " + exception.getMessage());
}
continue;
}
if (!active) continue;
final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class);
if (reloadServer != null) {
RELOAD_SERVER.add(new EmptyInvokerHolder<>(protocol, method, reloadServer));
continue;
}
final ProtocolHandler.ReloadDataPack reloadDataPack = method.getAnnotation(ProtocolHandler.ReloadDataPack.class);
if (reloadDataPack != null) {
RELOAD_DATAPACK.add(new EmptyInvokerHolder<>(protocol, method, reloadDataPack));
continue;
}
final ProtocolHandler.PayloadReceiver payloadReceiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class);
if (payloadReceiver != null) {
PAYLOAD_RECEIVERS.put(payloadReceiver.payload(), new PayloadReceiverInvokerHolder(protocol, method, payloadReceiver));
continue;
}
final ProtocolHandler.BytebufReceiver bytebufReceiver = method.getAnnotation(ProtocolHandler.BytebufReceiver.class);
if (bytebufReceiver != null) {
String key = bytebufReceiver.key();
BytebufReceiverInvokerHolder holder = new BytebufReceiverInvokerHolder(protocol, method, bytebufReceiver);
if (bytebufReceiver.onlyNamespace()) {
NAMESPACED_BYTEBUF_RECEIVERS.put(key.isEmpty() ? register.namespace() : key, holder);
} else {
if (key.isEmpty()) {
GENERIC_BYTEBUF_RECEIVERS.add(holder);
} else {
if (key.contains(":")) {
STRICT_BYTEBUF_RECEIVERS.put(key, holder);
} else {
STRICT_BYTEBUF_RECEIVERS.put(register.namespace() + ":" + key, holder);
}
}
}
continue;
}
final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class);
if (ticker != null) {
TICKERS.add(new EmptyInvokerHolder<>(protocol, method, ticker));
continue;
}
final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class);
if (playerJoin != null) {
PLAYER_JOIN.add(new PlayerInvokerHolder<>(protocol, method, playerJoin));
continue;
}
final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class);
if (playerLeave != null) {
PLAYER_LEAVE.add(new PlayerInvokerHolder<>(protocol, method, playerLeave));
continue;
}
final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class);
if (minecraftRegister != null) {
String key = minecraftRegister.key();
MinecraftRegisterInvokerHolder holder = new MinecraftRegisterInvokerHolder(protocol, method, minecraftRegister);
if (minecraftRegister.onlyNamespace()) {
NAMESPACED_MINECRAFT_REGISTER.put(key.isEmpty() ? register.namespace() : key, holder);
} else {
if (key.isEmpty()) {
WILD_MINECRAFT_REGISTER.add(holder);
} else {
if (key.contains(":")) {
STRICT_MINECRAFT_REGISTER.put(key, holder);
} else {
STRICT_MINECRAFT_REGISTER.put(register.namespace() + ":" + key, holder);
}
}
}
}
}
}
for (var idInfo : IDS.entrySet()) {
var codec = CODECS.get(idInfo.getKey());
if (codec == null) {
throw new IllegalArgumentException("Payload " + idInfo.getKey() + " is not configured correctly");
}
ID2CODEC.put(idInfo.getValue(), codec);
}
// Log all found instances
LOGGER.info("Protocol initialization complete. Found instances:");
LOGGER.info(" Payload receivers: " + PAYLOAD_RECEIVERS.size());
PAYLOAD_RECEIVERS.forEach((clazz, holder) ->
LOGGER.info(" " + clazz.getSimpleName() + " -> " + holder.getClass().getSimpleName()));
LOGGER.info(" IDs: " + IDS.size());
IDS.forEach((clazz, id) ->
LOGGER.info(" " + clazz.getSimpleName() + " -> " + id));
LOGGER.info(" Codecs: " + CODECS.size());
CODECS.forEach((clazz, codec) ->
LOGGER.info(" " + clazz.getSimpleName() + " -> " + codec.getClass().getSimpleName()));
LOGGER.info(" Strict bytebuf receivers: " + STRICT_BYTEBUF_RECEIVERS.size());
STRICT_BYTEBUF_RECEIVERS.forEach((key, holder) ->
LOGGER.info(" " + key + " -> " + holder.getClass().getSimpleName()));
LOGGER.info(" Namespaced bytebuf receivers: " + NAMESPACED_BYTEBUF_RECEIVERS.size());
NAMESPACED_BYTEBUF_RECEIVERS.forEach((key, holder) ->
LOGGER.info(" " + key + " -> " + holder.getClass().getSimpleName()));
LOGGER.info(" Generic bytebuf receivers: " + GENERIC_BYTEBUF_RECEIVERS.size());
GENERIC_BYTEBUF_RECEIVERS.forEach(holder ->
LOGGER.info(" " + holder.getClass().getSimpleName()));
LOGGER.info(" Tickers: " + TICKERS.size());
LOGGER.info(" Player join handlers: " + PLAYER_JOIN.size());
LOGGER.info(" Player leave handlers: " + PLAYER_LEAVE.size());
LOGGER.info(" Server reload handlers: " + RELOAD_SERVER.size());
LOGGER.info(" DataPack reload handlers: " + RELOAD_DATAPACK.size());
LOGGER.info(" Strict minecraft register: " + STRICT_MINECRAFT_REGISTER.size());
STRICT_MINECRAFT_REGISTER.forEach((key, holder) ->
LOGGER.info(" " + key + " -> " + holder.getClass().getSimpleName()));
LOGGER.info(" Namespaced minecraft register: " + NAMESPACED_MINECRAFT_REGISTER.size());
NAMESPACED_MINECRAFT_REGISTER.forEach((key, holder) ->
LOGGER.info(" " + key + " -> " + holder.getClass().getSimpleName()));
LOGGER.info(" Wild minecraft register: " + WILD_MINECRAFT_REGISTER.size());
WILD_MINECRAFT_REGISTER.forEach(holder ->
LOGGER.info(" " + holder.getClass().getSimpleName()));
}
public static LeavesCustomPayload decode(ResourceLocation location, FriendlyByteBuf buf) {
var codec = ID2CODEC.get(location);
if (codec == null) {
return null;
}
return codec.decode(ProtocolUtils.decorate(buf));
}
public static void encode(FriendlyByteBuf buf, LeavesCustomPayload payload) {
var location = IDS.get(payload.getClass());
var codec = CODECS.get(payload.getClass());
if (location == null || codec == null) {
throw new IllegalArgumentException("Payload " + payload.getClass() + " is not configured correctly " + location + " " + codec);
}
buf.writeResourceLocation(location);
codec.encode(ProtocolUtils.decorate(buf), payload);
}
public static void handlePayload(ServerPlayer player, LeavesCustomPayload payload) {
PayloadReceiverInvokerHolder holder;
if ((holder = PAYLOAD_RECEIVERS.get(payload.getClass())) != null) {
holder.invoke(player, payload);
}
}
public static boolean handleBytebuf(ServerPlayer player, ResourceLocation location, ByteBuf buf) {
RegistryFriendlyByteBuf buf1 = ProtocolUtils.decorate(buf);
BytebufReceiverInvokerHolder holder;
if ((holder = STRICT_BYTEBUF_RECEIVERS.get(location.toString())) != null) {
holder.invoke(player, buf1);
return true;
}
if ((holder = NAMESPACED_BYTEBUF_RECEIVERS.get(location.getNamespace())) != null) {
if (holder.invoke(player, buf1)) {
return true;
}
}
for (var holder1 : GENERIC_BYTEBUF_RECEIVERS) {
if (holder1.invoke(player, buf1)) {
return true;
}
}
return false;
}
public static void handleTick(long tickCount) {
for (var tickerInfo : TICKERS) {
if (tickCount % tickerInfo.owner().tickerInterval(tickerInfo.handler().tickerId()) == 0) {
tickerInfo.invoke();
}
}
}
public static void handlePlayerJoin(ServerPlayer player) {
sendKnownId(player);
for (var join : PLAYER_JOIN) {
join.invoke(player);
}
}
public static void handlePlayerLeave(ServerPlayer player) {
for (var leave : PLAYER_LEAVE) {
leave.invoke(player);
}
}
public static void handleServerReload() {
for (var reload : RELOAD_SERVER) {
reload.invoke();
}
}
public static void handleDataPackReload() {
for (var reload : RELOAD_DATAPACK) {
reload.invoke();
}
}
public static void handleMinecraftRegister(String channelId, PluginMessageBridgeImpl bridge) {
ServerPlayer player = null;
if (bridge instanceof CraftPlayer craftPlayer) {
player = craftPlayer.getHandle();
}
if (player == null) {
return;
}
ResourceLocation location = ResourceLocation.tryParse(channelId);
if (location == null) {
return;
}
for (var wildHolder : WILD_MINECRAFT_REGISTER) {
wildHolder.invoke(player, location);
}
MinecraftRegisterInvokerHolder holder;
if ((holder = STRICT_MINECRAFT_REGISTER.get(location.toString())) != null) {
holder.invoke(player, location);
}
if ((holder = NAMESPACED_MINECRAFT_REGISTER.get(location.getNamespace())) != null) {
holder.invoke(player, location);
}
}
private static void sendKnownId(ServerPlayer player) {
Set<String> set = new HashSet<>();
PAYLOAD_RECEIVERS.forEach((clazz, holder) -> set.add(IDS.get(clazz).toString()));
STRICT_BYTEBUF_RECEIVERS.forEach((key, holder) -> set.add(key));
if (set.isEmpty()) return;
ProtocolUtils.sendBytebufPacket(player, ResourceLocation.fromNamespaceAndPath("minecraft", "register"), buf -> {
for (String channel : set) {
buf.writeBytes(channel.getBytes(StandardCharsets.US_ASCII));
buf.writeByte(0);
}
buf.writerIndex(Math.max(buf.writerIndex() - 1, 0));
});
}
public static Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
findClassesInPackageByFile(pack, filePath, classes);
} else if ("jar".equals(protocol)) {
JarFile jar;
try {
jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(pack, entries, packageDirName, classes);
} catch (IOException exception) {
LOGGER.warning("Failed to load jar file, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
} catch (IOException exception) {
LOGGER.warning("Failed to load classes, " + exception.getCause() + ": " + exception.getMessage());
}
return classes;
}
private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
if (dirfiles != null) {
for (File file : dirfiles) {
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
package org.leavesmc.leaves.protocol.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class ProtocolHandler {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Init {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayloadReceiver {
Class<? extends LeavesCustomPayload> payload();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BytebufReceiver {
String key() default "";
boolean onlyNamespace() default false;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ticker {
String tickerId() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerJoin {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerLeave {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReloadServer {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MinecraftRegister {
String key() default "";
boolean onlyNamespace() default false;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReloadDataPack {
}
}

View File

@@ -0,0 +1,43 @@
package org.leavesmc.leaves.protocol.core;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.papermc.paper.ServerBuildInfo;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
import java.util.function.Function;
public class ProtocolUtils {
private static final Function<ByteBuf, RegistryFriendlyByteBuf> bufDecorator = buf -> buf instanceof RegistryFriendlyByteBuf registry ? registry : new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess());
public static String buildProtocolVersion(String protocol) {
return protocol + "-leaves-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE);
}
public static void sendEmptyPacket(ServerPlayer player, ResourceLocation id) {
player.internalConnection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, null)));
}
public static void sendBytebufPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer<? super RegistryFriendlyByteBuf> consumer) {
RegistryFriendlyByteBuf buf = decorate(Unpooled.buffer());
consumer.accept(buf);
player.internalConnection.send(new ClientboundCustomPayloadPacket(new DiscardedPayload(id, ByteBufUtil.getBytes(buf))));
}
public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) {
player.internalConnection.send(new ClientboundCustomPayloadPacket(payload));
}
public static RegistryFriendlyByteBuf decorate(ByteBuf buf) {
return bufDecorator.apply(buf);
}
}

View File

@@ -0,0 +1,69 @@
package org.leavesmc.leaves.protocol.core.invoker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public abstract class AbstractInvokerHolder<T> {
protected final LeavesProtocol owner;
protected final Method invoker;
protected final T handler;
protected final Class<?> returnType;
protected final Class<?>[] parameterTypes;
protected AbstractInvokerHolder(LeavesProtocol owner, Method invoker, T handler, @Nullable Class<?> returnType, @NotNull Class<?>... parameterTypes) {
this.owner = owner;
this.invoker = invoker;
this.handler = handler;
this.returnType = returnType;
this.parameterTypes = parameterTypes;
validateMethodSignature();
}
protected void validateMethodSignature() {
if (returnType != null && !returnType.isAssignableFrom(invoker.getReturnType())) {
throw new IllegalArgumentException("Return type mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
": expected " + returnType.getName() + " but found " + invoker.getReturnType().getName());
}
Class<?>[] methodParamTypes = invoker.getParameterTypes();
if (methodParamTypes.length != parameterTypes.length) {
throw new IllegalArgumentException("Parameter count mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
": expected " + parameterTypes.length + " but found " + methodParamTypes.length);
}
for (int i = 0; i < parameterTypes.length; i++) {
if (!parameterTypes[i].isAssignableFrom(methodParamTypes[i])) {
throw new IllegalArgumentException("Parameter type mismatch in " + owner.getClass().getName() + "#" + invoker.getName() +
" at index " + i + ": expected " + parameterTypes[i].getName() + " but found " + methodParamTypes[i].getName());
}
}
}
public LeavesProtocol owner() {
return owner;
}
public T handler() {
return handler;
}
protected Object invoke0(boolean force, Object... args) {
if (!force && !owner.isActive()) {
return null;
}
try {
if (Modifier.isStatic(invoker.getModifiers())) {
return invoker.invoke(null, args);
} else {
return invoker.invoke(owner, args);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class BytebufReceiverInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.BytebufReceiver> {
public BytebufReceiverInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.BytebufReceiver handler) {
super(owner, invoker, handler, null, ServerPlayer.class, FriendlyByteBuf.class);
}
public boolean invoke(ServerPlayer player, FriendlyByteBuf buf) {
return invoke0(false, player, buf) instanceof Boolean b && b;
}
}

View File

@@ -0,0 +1,15 @@
package org.leavesmc.leaves.protocol.core.invoker;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.Method;
public class EmptyInvokerHolder<T> extends AbstractInvokerHolder<T> {
public EmptyInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
super(owner, invoker, handler, null);
}
public void invoke() {
invoke0(false);
}
}

View File

@@ -0,0 +1,16 @@
package org.leavesmc.leaves.protocol.core.invoker;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class InitInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.Init> {
public InitInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.Init handler) {
super(owner, invoker, handler, null);
}
public void invoke() {
invoke0(true);
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class MinecraftRegisterInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.MinecraftRegister> {
public MinecraftRegisterInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.MinecraftRegister handler) {
super(owner, invoker, handler, null, ServerPlayer.class, ResourceLocation.class);
}
public void invoke(ServerPlayer player, ResourceLocation id) {
invoke0(false, player, id);
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import java.lang.reflect.Method;
public class PayloadReceiverInvokerHolder extends AbstractInvokerHolder<ProtocolHandler.PayloadReceiver> {
public PayloadReceiverInvokerHolder(LeavesProtocol owner, Method invoker, ProtocolHandler.PayloadReceiver handler) {
super(owner, invoker, handler, null, ServerPlayer.class, handler.payload());
}
public void invoke(ServerPlayer player, LeavesCustomPayload payload) {
invoke0(false, player, payload);
}
}

View File

@@ -0,0 +1,16 @@
package org.leavesmc.leaves.protocol.core.invoker;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import java.lang.reflect.Method;
public class PlayerInvokerHolder<T> extends AbstractInvokerHolder<T> {
public PlayerInvokerHolder(LeavesProtocol owner, Method invoker, T handler) {
super(owner, invoker, handler, null, ServerPlayer.class);
}
public void invoke(ServerPlayer player) {
invoke0(false, player);
}
}

View File

@@ -0,0 +1,290 @@
package org.leavesmc.leaves.protocol.jade;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.Chicken;
import net.minecraft.world.entity.animal.allay.Allay;
import net.minecraft.world.entity.animal.armadillo.Armadillo;
import net.minecraft.world.entity.animal.frog.Tadpole;
import net.minecraft.world.entity.animal.sniffer.Sniffer;
import net.minecraft.world.entity.monster.ZombieVillager;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity;
import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraft.world.level.block.entity.ComparatorBlockEntity;
import net.minecraft.world.level.block.entity.HopperBlockEntity;
import net.minecraft.world.level.block.entity.JukeboxBlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity;
import org.bukkit.Bukkit;
import org.bxteam.divinemc.config.DivineConfig;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.payload.ClientHandshakePayload;
import org.leavesmc.leaves.protocol.jade.payload.ReceiveDataPayload;
import org.leavesmc.leaves.protocol.jade.payload.RequestBlockPayload;
import org.leavesmc.leaves.protocol.jade.payload.RequestEntityPayload;
import org.leavesmc.leaves.protocol.jade.payload.ServerHandshakePayload;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider;
import org.leavesmc.leaves.protocol.jade.provider.ItemStorageExtensionProvider;
import org.leavesmc.leaves.protocol.jade.provider.ItemStorageProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.BeehiveProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.BlockNameProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.BrewingStandProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.CampfireProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.ChiseledBookshelfProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.CommandBlockProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.FurnaceProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.HopperLockProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.JukeboxProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.LecternProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.MobSpawnerCooldownProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.RedstoneProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.AnimalOwnerProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.MobBreedingProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.MobGrowthProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.NextEntityDropProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.PetArmorProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.StatusEffectsProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.ZombieVillagerProvider;
import org.leavesmc.leaves.protocol.jade.util.HierarchyLookup;
import org.leavesmc.leaves.protocol.jade.util.LootTableMineableCollector;
import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup;
import org.leavesmc.leaves.protocol.jade.util.PriorityStore;
import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup;
import org.leavesmc.leaves.util.NbtUtils;
import org.purpurmc.purpur.util.MinecraftInternalPlugin;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@LeavesProtocol.Register(namespace = "jade")
public class JadeProtocol implements LeavesProtocol {
public static final String PROTOCOL_ID = "jade";
public static final String PROTOCOL_VERSION = "7";
public static final HierarchyLookup<IServerDataProvider<EntityAccessor>> entityDataProviders = new HierarchyLookup<>(Entity.class);
public static final PairHierarchyLookup<IServerDataProvider<BlockAccessor>> blockDataProviders = new PairHierarchyLookup<>(new HierarchyLookup<>(Block.class), new HierarchyLookup<>(BlockEntity.class));
public static final WrappedHierarchyLookup<IServerExtensionProvider<ItemStack>> itemStorageProviders = WrappedHierarchyLookup.forAccessor();
private static final Set<ServerPlayer> enabledPlayers = new HashSet<>();
private static final org.purpurmc.purpur.util.MinecraftInternalPlugin minecraftInternalPlugin = new org.purpurmc.purpur.util.MinecraftInternalPlugin();
public static PriorityStore<ResourceLocation, IJadeProvider> priorities;
private static List<Block> shearableBlocks = null;
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@Contract("_ -> new")
public static @NotNull ResourceLocation mc_id(String path) {
return ResourceLocation.withDefaultNamespace(path);
}
@ProtocolHandler.Init
public static void init() {
priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid);
// core plugin
blockDataProviders.register(BlockEntity.class, BlockNameProvider.INSTANCE);
// universal plugin
entityDataProviders.register(Entity.class, ItemStorageProvider.getEntity());
blockDataProviders.register(Block.class, ItemStorageProvider.getBlock());
itemStorageProviders.register(Object.class, ItemStorageExtensionProvider.INSTANCE);
itemStorageProviders.register(Block.class, ItemStorageExtensionProvider.INSTANCE);
// vanilla plugin
entityDataProviders.register(Entity.class, AnimalOwnerProvider.INSTANCE);
entityDataProviders.register(LivingEntity.class, StatusEffectsProvider.INSTANCE);
entityDataProviders.register(AgeableMob.class, MobGrowthProvider.INSTANCE);
entityDataProviders.register(Tadpole.class, MobGrowthProvider.INSTANCE);
entityDataProviders.register(Animal.class, MobBreedingProvider.INSTANCE);
entityDataProviders.register(Allay.class, MobBreedingProvider.INSTANCE);
entityDataProviders.register(Mob.class, PetArmorProvider.INSTANCE);
entityDataProviders.register(Chicken.class, NextEntityDropProvider.INSTANCE);
entityDataProviders.register(Armadillo.class, NextEntityDropProvider.INSTANCE);
entityDataProviders.register(Sniffer.class, NextEntityDropProvider.INSTANCE);
entityDataProviders.register(ZombieVillager.class, ZombieVillagerProvider.INSTANCE);
blockDataProviders.register(BrewingStandBlockEntity.class, BrewingStandProvider.INSTANCE);
blockDataProviders.register(BeehiveBlockEntity.class, BeehiveProvider.INSTANCE);
blockDataProviders.register(CommandBlockEntity.class, CommandBlockProvider.INSTANCE);
blockDataProviders.register(JukeboxBlockEntity.class, JukeboxProvider.INSTANCE);
blockDataProviders.register(LecternBlockEntity.class, LecternProvider.INSTANCE);
blockDataProviders.register(ComparatorBlockEntity.class, RedstoneProvider.INSTANCE);
blockDataProviders.register(HopperBlockEntity.class, HopperLockProvider.INSTANCE);
blockDataProviders.register(CalibratedSculkSensorBlockEntity.class, RedstoneProvider.INSTANCE);
blockDataProviders.register(AbstractFurnaceBlockEntity.class, FurnaceProvider.INSTANCE);
blockDataProviders.register(ChiseledBookShelfBlockEntity.class, ChiseledBookshelfProvider.INSTANCE);
blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE);
itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE);
blockDataProviders.idMapped();
entityDataProviders.idMapped();
blockDataProviders.loadComplete(priorities);
entityDataProviders.loadComplete(priorities);
itemStorageProviders.loadComplete(priorities);
rebuildShearableBlocks();
}
@ProtocolHandler.PayloadReceiver(payload = ClientHandshakePayload.class)
public static void clientHandshake(ServerPlayer player, ClientHandshakePayload payload) {
if (!payload.protocolVersion().equals(PROTOCOL_VERSION)) {
player.sendSystemMessage(Component.literal("You are using a different version of Jade than the server. Please update Jade or report to the server operator").withColor(0xff0000));
return;
}
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
enabledPlayers.add(player);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLeave(ServerPlayer player) {
enabledPlayers.remove(player);
}
@ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class)
public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) {
Bukkit.getGlobalRegionScheduler().run(minecraftInternalPlugin, (task) -> {
EntityAccessor accessor = payload.data().unpack(player);
if (accessor == null) {
return;
}
Entity entity = accessor.getEntity();
double maxDistance = Mth.square(player.entityInteractionRange() + 21);
if (entity == null || player.distanceToSqr(entity) > maxDistance) {
return;
}
List<IServerDataProvider<EntityAccessor>> providers = entityDataProviders.get(entity);
if (providers.isEmpty()) {
return;
}
CompoundTag tag = new CompoundTag();
for (IServerDataProvider<EntityAccessor> provider : providers) {
if (!payload.dataProviders().contains(provider)) {
continue;
}
try {
provider.appendServerData(tag, accessor);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Error while saving data for entity " + entity);
}
}
tag.putInt("EntityId", entity.getId());
ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag));
});
}
@ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class)
public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) {
Bukkit.getGlobalRegionScheduler().run(minecraftInternalPlugin, (task) -> {
BlockAccessor accessor = payload.data().unpack(player);
if (accessor == null) {
return;
}
BlockPos pos = accessor.getPosition();
Block block = accessor.getBlock();
BlockEntity blockEntity = accessor.getBlockEntity();
double maxDistance = Mth.square(player.blockInteractionRange() + 21);
if (pos.distSqr(player.blockPosition()) > maxDistance || !accessor.getLevel().isLoaded(pos)) {
return;
}
List<IServerDataProvider<BlockAccessor>> providers;
if (blockEntity != null) {
providers = blockDataProviders.getMerged(block, blockEntity);
} else {
providers = blockDataProviders.first.get(block);
}
if (providers.isEmpty()) {
return;
}
CompoundTag tag = new CompoundTag();
for (IServerDataProvider<BlockAccessor> provider : providers) {
if (!payload.dataProviders().contains(provider)) {
continue;
}
try {
provider.appendServerData(tag, accessor);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Error while saving data for block " + accessor.getBlockState());
}
}
NbtUtils.writeBlockPosToTag(pos, tag);
tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString());
ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag));
});
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
rebuildShearableBlocks();
for (ServerPlayer player : enabledPlayers) {
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
}
}
private static void rebuildShearableBlocks() {
try {
shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute(
MinecraftServer.getServer().reloadableRegistries().lookup().lookupOrThrow(Registries.LOOT_TABLE),
Items.SHEARS.getDefaultInstance()
));
} catch (Throwable ignore) {
shearableBlocks = List.of();
LeavesLogger.LOGGER.severe("Failed to collect shearable blocks");
}
}
@Override
public boolean isActive() {
return DivineConfig.NetworkCategory.protocolsJadeEnabled;
}
}

View File

@@ -0,0 +1,22 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamEncoder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.Nullable;
public interface Accessor<T extends HitResult> {
Level getLevel();
Player getPlayer();
<D> Tag encodeAsNbt(StreamEncoder<RegistryFriendlyByteBuf, D> codec, D value);
T getHitResult();
@Nullable
Object getTarget();
}

View File

@@ -0,0 +1,60 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.ByteArrayTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamEncoder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import org.apache.commons.lang3.ArrayUtils;
import java.util.function.Supplier;
public abstract class AccessorImpl<T extends HitResult> implements Accessor<T> {
private final Level level;
private final Player player;
private final Supplier<T> hit;
protected boolean verify;
private RegistryFriendlyByteBuf buffer;
public AccessorImpl(Level level, Player player, Supplier<T> hit) {
this.level = level;
this.player = player;
this.hit = hit;
}
@Override
public Level getLevel() {
return level;
}
@Override
public Player getPlayer() {
return player;
}
private RegistryFriendlyByteBuf buffer() {
if (buffer == null) {
buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess());
}
buffer.clear();
return buffer;
}
@Override
public <D> Tag encodeAsNbt(StreamEncoder<RegistryFriendlyByteBuf, D> streamCodec, D value) {
RegistryFriendlyByteBuf buffer = buffer();
streamCodec.encode(buffer, value);
ByteArrayTag tag = new ByteArrayTag(ArrayUtils.subarray(buffer.array(), 0, buffer.readableBytes()));
buffer.clear();
return tag;
}
@Override
public T getHitResult() {
return hit.get();
}
}

View File

@@ -0,0 +1,44 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Supplier;
public interface BlockAccessor extends Accessor<BlockHitResult> {
Block getBlock();
BlockState getBlockState();
BlockEntity getBlockEntity();
BlockPos getPosition();
@ApiStatus.NonExtendable
interface Builder {
Builder level(Level level);
Builder player(Player player);
Builder hit(BlockHitResult hit);
Builder blockState(BlockState state);
default Builder blockEntity(BlockEntity blockEntity) {
return blockEntity(() -> blockEntity);
}
Builder blockEntity(Supplier<BlockEntity> blockEntity);
Builder from(BlockAccessor accessor);
BlockAccessor build();
}
}

View File

@@ -0,0 +1,143 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import com.google.common.base.Suppliers;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/**
* Class to get information of block target and context.
*/
public class BlockAccessorImpl extends AccessorImpl<BlockHitResult> implements BlockAccessor {
private final BlockState blockState;
@Nullable
private final Supplier<BlockEntity> blockEntity;
private BlockAccessorImpl(Builder builder) {
super(builder.level, builder.player, Suppliers.ofInstance(builder.hit));
blockState = builder.blockState;
blockEntity = builder.blockEntity;
}
@Override
public Block getBlock() {
return getBlockState().getBlock();
}
@Override
public BlockState getBlockState() {
return blockState;
}
@Override
public BlockEntity getBlockEntity() {
return blockEntity == null ? null : blockEntity.get();
}
@Override
public BlockPos getPosition() {
return getHitResult().getBlockPos();
}
@Nullable
@Override
public Object getTarget() {
return getBlockEntity();
}
public static class Builder implements BlockAccessor.Builder {
private Level level;
private Player player;
private BlockHitResult hit;
private BlockState blockState = Blocks.AIR.defaultBlockState();
private Supplier<BlockEntity> blockEntity;
@Override
public Builder level(Level level) {
this.level = level;
return this;
}
@Override
public Builder player(Player player) {
this.player = player;
return this;
}
@Override
public Builder hit(BlockHitResult hit) {
this.hit = hit;
return this;
}
@Override
public Builder blockState(BlockState blockState) {
this.blockState = blockState;
return this;
}
@Override
public Builder blockEntity(Supplier<BlockEntity> blockEntity) {
this.blockEntity = blockEntity;
return this;
}
@Override
public Builder from(BlockAccessor accessor) {
level = accessor.getLevel();
player = accessor.getPlayer();
hit = accessor.getHitResult();
blockEntity = accessor::getBlockEntity;
blockState = accessor.getBlockState();
return this;
}
@Override
public BlockAccessor build() {
return new BlockAccessorImpl(this);
}
}
public record SyncData(boolean showDetails, BlockHitResult hit, BlockState blockState, ItemStack fakeBlock) {
public static final StreamCodec<RegistryFriendlyByteBuf, SyncData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL,
SyncData::showDetails,
StreamCodec.of(FriendlyByteBuf::writeBlockHitResult, FriendlyByteBuf::readBlockHitResult),
SyncData::hit,
ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY),
SyncData::blockState,
ItemStack.OPTIONAL_STREAM_CODEC,
SyncData::fakeBlock,
SyncData::new
);
public BlockAccessor unpack(ServerPlayer player) {
Supplier<BlockEntity> blockEntity = null;
if (blockState.hasBlockEntity()) {
blockEntity = Suppliers.memoize(() -> player.level().getBlockEntity(hit.getBlockPos()));
}
return new Builder()
.level(player.level())
.player(player)
.hit(hit)
.blockState(blockState)
.blockEntity(blockEntity)
.build();
}
}
}

View File

@@ -0,0 +1,42 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Supplier;
public interface EntityAccessor extends Accessor<EntityHitResult> {
Entity getEntity();
/**
* For part entity like ender dragon's, getEntity() will return the parent entity.
*/
Entity getRawEntity();
@ApiStatus.NonExtendable
interface Builder {
Builder level(Level level);
Builder player(Player player);
default Builder hit(EntityHitResult hit) {
return hit(() -> hit);
}
Builder hit(Supplier<EntityHitResult> hit);
default Builder entity(Entity entity) {
return entity(() -> entity);
}
Builder entity(Supplier<Entity> entity);
Builder from(EntityAccessor accessor);
EntityAccessor build();
}
}

View File

@@ -0,0 +1,112 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import com.google.common.base.Suppliers;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.util.CommonUtil;
import java.util.function.Supplier;
public class EntityAccessorImpl extends AccessorImpl<EntityHitResult> implements EntityAccessor {
private final Supplier<Entity> entity;
public EntityAccessorImpl(Builder builder) {
super(builder.level, builder.player, builder.hit);
entity = builder.entity;
}
@Override
public Entity getEntity() {
return CommonUtil.wrapPartEntityParent(getRawEntity());
}
@Override
public Entity getRawEntity() {
return entity.get();
}
@NotNull
@Override
public Object getTarget() {
return getEntity();
}
public static class Builder implements EntityAccessor.Builder {
private Level level;
private Player player;
private Supplier<EntityHitResult> hit;
private Supplier<Entity> entity;
@Override
public Builder level(Level level) {
this.level = level;
return this;
}
@Override
public Builder player(Player player) {
this.player = player;
return this;
}
@Override
public Builder hit(Supplier<EntityHitResult> hit) {
this.hit = hit;
return this;
}
@Override
public Builder entity(Supplier<Entity> entity) {
this.entity = entity;
return this;
}
@Override
public Builder from(EntityAccessor accessor) {
level = accessor.getLevel();
player = accessor.getPlayer();
hit = accessor::getHitResult;
entity = accessor::getEntity;
return this;
}
@Override
public EntityAccessor build() {
return new EntityAccessorImpl(this);
}
}
public record SyncData(boolean showDetails, int id, int partIndex, Vec3 hitVec) {
public static final StreamCodec<RegistryFriendlyByteBuf, SyncData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL,
SyncData::showDetails,
ByteBufCodecs.VAR_INT,
SyncData::id,
ByteBufCodecs.VAR_INT,
SyncData::partIndex,
ByteBufCodecs.VECTOR3F.map(Vec3::new, Vec3::toVector3f),
SyncData::hitVec,
SyncData::new
);
public EntityAccessor unpack(ServerPlayer player) {
Supplier<Entity> entity = Suppliers.memoize(() -> CommonUtil.getPartEntity(player.level().getEntity(id), partIndex));
return new EntityAccessorImpl.Builder()
.level(player.level())
.player(player)
.entity(entity)
.hit(Suppliers.memoize(() -> new EntityHitResult(entity.get(), hitVec)))
.build();
}
}
}

View File

@@ -0,0 +1,19 @@
package org.leavesmc.leaves.protocol.jade.payload;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
public record ClientHandshakePayload(String protocolVersion) implements LeavesCustomPayload {
@ID
private static final ResourceLocation PACKET_CLIENT_HANDSHAKE = JadeProtocol.id("client_handshake");
@Codec
private static final StreamCodec<RegistryFriendlyByteBuf, ClientHandshakePayload> CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, ClientHandshakePayload::protocolVersion, ClientHandshakePayload::new
);
}

View File

@@ -0,0 +1,20 @@
package org.leavesmc.leaves.protocol.jade.payload;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload {
@ID
private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data");
@Codec
private static final StreamCodec<FriendlyByteBuf, ReceiveDataPayload> CODEC = StreamCodec.composite(
ByteBufCodecs.COMPOUND_TAG, ReceiveDataPayload::tag, ReceiveDataPayload::new
);
}

View File

@@ -0,0 +1,35 @@
package org.leavesmc.leaves.protocol.jade.payload;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessorImpl;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
import java.util.List;
import java.util.Objects;
import static org.leavesmc.leaves.protocol.jade.JadeProtocol.blockDataProviders;
public record RequestBlockPayload(BlockAccessorImpl.SyncData data, List<@Nullable IServerDataProvider<BlockAccessor>> dataProviders) implements LeavesCustomPayload {
@ID
private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block");
@Codec
private static final StreamCodec<RegistryFriendlyByteBuf, RequestBlockPayload> CODEC = StreamCodec.composite(
BlockAccessorImpl.SyncData.STREAM_CODEC,
RequestBlockPayload::data,
ByteBufCodecs.<ByteBuf, IServerDataProvider<BlockAccessor>>list()
.apply(ByteBufCodecs.idMapper(
$ -> Objects.requireNonNull(blockDataProviders.idMapper()).byId($),
$ -> Objects.requireNonNull(blockDataProviders.idMapper()).getIdOrThrow($))),
RequestBlockPayload::dataProviders,
RequestBlockPayload::new);
}

View File

@@ -0,0 +1,36 @@
package org.leavesmc.leaves.protocol.jade.payload;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessorImpl;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
import java.util.List;
import java.util.Objects;
import static org.leavesmc.leaves.protocol.jade.JadeProtocol.entityDataProviders;
public record RequestEntityPayload(EntityAccessorImpl.SyncData data, List<@Nullable IServerDataProvider<EntityAccessor>> dataProviders) implements LeavesCustomPayload {
@ID
private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity");
@Codec
private static final StreamCodec<RegistryFriendlyByteBuf, RequestEntityPayload> CODEC = StreamCodec.composite(
EntityAccessorImpl.SyncData.STREAM_CODEC,
RequestEntityPayload::data,
ByteBufCodecs.<ByteBuf, IServerDataProvider<EntityAccessor>>list()
.apply(ByteBufCodecs.idMapper(
$ -> Objects.requireNonNull(entityDataProviders.idMapper()).byId($),
$ -> Objects.requireNonNull(entityDataProviders.idMapper()).getIdOrThrow($)
)),
RequestEntityPayload::dataProviders,
RequestEntityPayload::new);
}

View File

@@ -0,0 +1,37 @@
package org.leavesmc.leaves.protocol.jade.payload;
import com.google.common.collect.Maps;
import io.netty.buffer.ByteBuf;
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.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import java.util.List;
import java.util.Map;
import static org.leavesmc.leaves.protocol.jade.util.JadeCodec.PRIMITIVE_STREAM_CODEC;
public record ServerHandshakePayload(Map<ResourceLocation, Object> serverConfig, List<Block> shearableBlocks, List<ResourceLocation> blockProviderIds, List<ResourceLocation> entityProviderIds) implements LeavesCustomPayload {
@ID
private static final ResourceLocation PACKET_SERVER_HANDSHAKE = JadeProtocol.id("server_handshake");
@Codec
private static final StreamCodec<RegistryFriendlyByteBuf, ServerHandshakePayload> CODEC = StreamCodec.composite(
ByteBufCodecs.map(Maps::newHashMapWithExpectedSize, ResourceLocation.STREAM_CODEC, PRIMITIVE_STREAM_CODEC),
ServerHandshakePayload::serverConfig,
ByteBufCodecs.registry(Registries.BLOCK).apply(ByteBufCodecs.list()),
ServerHandshakePayload::shearableBlocks,
ByteBufCodecs.<ByteBuf, ResourceLocation>list().apply(ResourceLocation.STREAM_CODEC),
ServerHandshakePayload::blockProviderIds,
ByteBufCodecs.<ByteBuf, ResourceLocation>list().apply(ResourceLocation.STREAM_CODEC),
ServerHandshakePayload::entityProviderIds,
ServerHandshakePayload::new
);
}

View File

@@ -0,0 +1,12 @@
package org.leavesmc.leaves.protocol.jade.provider;
import net.minecraft.resources.ResourceLocation;
public interface IJadeProvider {
ResourceLocation getUid();
default int getDefaultPriority() {
return 0;
}
}

View File

@@ -0,0 +1,8 @@
package org.leavesmc.leaves.protocol.jade.provider;
import net.minecraft.nbt.CompoundTag;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
public interface IServerDataProvider<T extends Accessor<?>> extends IJadeProvider {
void appendServerData(CompoundTag data, T accessor);
}

View File

@@ -0,0 +1,10 @@
package org.leavesmc.leaves.protocol.jade.provider;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.util.ViewGroup;
import java.util.List;
public interface IServerExtensionProvider<T> extends IJadeProvider {
List<ViewGroup<T>> getGroups(Accessor<?> request);
}

View File

@@ -0,0 +1,142 @@
package org.leavesmc.leaves.protocol.jade.provider;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.LockCode;
import net.minecraft.world.RandomizableContainer;
import net.minecraft.world.WorldlyContainerHolder;
import net.minecraft.world.entity.animal.horse.AbstractHorse;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.ContainerEntity;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.EnderChestBlock;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.util.ItemCollector;
import org.leavesmc.leaves.protocol.jade.util.ItemIterator;
import org.leavesmc.leaves.protocol.jade.util.ViewGroup;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public enum ItemStorageExtensionProvider implements IServerExtensionProvider<ItemStack> {
INSTANCE;
public static final Cache<Object, ItemCollector<?>> targetCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(60, TimeUnit.SECONDS).build();
private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default");
public static ItemCollector<?> createItemCollector(Accessor<?> request) {
if (request.getTarget() instanceof AbstractHorse) {
return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> {
if (o instanceof AbstractHorse horse) {
return horse.inventory;
}
return null;
}, 2));
}
// TODO BlockEntity like fabric's ItemStorage
final Container container = findContainer(request);
if (container != null) {
if (container instanceof ChestBlockEntity) {
return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> {
if (o instanceof ChestBlockEntity blockEntity) {
if (blockEntity.getBlockState().getBlock() instanceof ChestBlock chestBlock) {
Container compound = null;
if (blockEntity.getLevel() != null) {
compound = ChestBlock.getContainer(chestBlock, blockEntity.getBlockState(), blockEntity.getLevel(), blockEntity.getBlockPos(), false);
}
if (compound != null) {
return compound;
}
}
return blockEntity;
}
return null;
}, 0));
}
return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0));
}
return ItemCollector.EMPTY;
}
public static @Nullable Container findContainer(@NotNull Accessor<?> accessor) {
Object target = accessor.getTarget();
if (target == null && accessor instanceof BlockAccessor blockAccessor &&
blockAccessor.getBlock() instanceof WorldlyContainerHolder holder) {
return holder.getContainer(blockAccessor.getBlockState(), accessor.getLevel(), blockAccessor.getPosition());
} else if (target instanceof Container container) {
return container;
}
return null;
}
@Override
public List<ViewGroup<ItemStack>> getGroups(Accessor<?> request) {
Object target = request.getTarget();
switch (target) {
case null -> {
return createItemCollector(request).update(request);
}
case RandomizableContainer te when te.getLootTable() != null -> {
return List.of();
}
case ContainerEntity containerEntity when containerEntity.getContainerLootTable() != null -> {
return List.of();
}
case EnderChestBlockEntity enderChest when request.getPlayer().getEnderChestInventory().isEmpty() -> {
return List.of();
}
default -> {
}
}
Player player = request.getPlayer();
if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) {
if (te.lockKey != LockCode.NO_LOCK) {
return List.of();
}
}
if (target instanceof EnderChestBlockEntity) {
PlayerEnderChestContainer inventory = player.getEnderChestInventory();
return new ItemCollector<>(new ItemIterator.ContainerItemIterator(x -> inventory, 0)).update(request);
}
ItemCollector<?> itemCollector;
try {
itemCollector = targetCache.get(target, () -> createItemCollector(request));
} catch (ExecutionException e) {
LeavesLogger.LOGGER.severe("Failed to get item collector for " + target);
return null;
}
return itemCollector.update(request);
}
@Override
public ResourceLocation getUid() {
return UNIVERSAL_ITEM_STORAGE;
}
@Override
public int getDefaultPriority() {
return 9999;
}
}

View File

@@ -0,0 +1,87 @@
package org.leavesmc.leaves.protocol.jade.provider;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.LockCode;
import net.minecraft.world.RandomizableContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.util.CommonUtil;
import org.leavesmc.leaves.protocol.jade.util.ItemCollector;
import org.leavesmc.leaves.protocol.jade.util.ViewGroup;
import java.util.List;
import java.util.Map;
public abstract class ItemStorageProvider<T extends Accessor<?>> implements IServerDataProvider<T> {
private static final StreamCodec<RegistryFriendlyByteBuf, Map.Entry<ResourceLocation, List<ViewGroup<ItemStack>>>> STREAM_CODEC = ViewGroup.listCodec(ItemStack.OPTIONAL_STREAM_CODEC);
private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage");
public static ForBlock getBlock() {
return ForBlock.INSTANCE;
}
public static ForEntity getEntity() {
return ForEntity.INSTANCE;
}
public static void putData(CompoundTag tag, @NotNull Accessor<?> accessor) {
Object target = accessor.getTarget();
Player player = accessor.getPlayer();
Map.Entry<ResourceLocation, List<ViewGroup<ItemStack>>> entry = CommonUtil.getServerExtensionData(accessor, JadeProtocol.itemStorageProviders);
if (entry != null) {
List<ViewGroup<ItemStack>> groups = entry.getValue();
for (ViewGroup<ItemStack> group : groups) {
if (group.views.size() > ItemCollector.MAX_SIZE) {
group.views = group.views.subList(0, ItemCollector.MAX_SIZE);
}
}
tag.put(UNIVERSAL_ITEM_STORAGE.toString(), accessor.encodeAsNbt(STREAM_CODEC, entry));
return;
}
if (target instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) {
tag.putBoolean("Loot", true);
} else if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) {
if (te.lockKey != LockCode.NO_LOCK) {
tag.putBoolean("Locked", true);
}
}
}
@Override
public ResourceLocation getUid() {
return UNIVERSAL_ITEM_STORAGE;
}
@Override
public void appendServerData(CompoundTag tag, @NotNull T accessor) {
if (accessor.getTarget() instanceof AbstractFurnaceBlockEntity) {
return;
}
putData(tag, accessor);
}
@Override
public int getDefaultPriority() {
return 1000;
}
public static class ForBlock extends ItemStorageProvider<BlockAccessor> {
private static final ForBlock INSTANCE = new ForBlock();
}
public static class ForEntity extends ItemStorageProvider<EntityAccessor> {
private static final ForEntity INSTANCE = new ForEntity();
}
}

View File

@@ -0,0 +1,23 @@
package org.leavesmc.leaves.protocol.jade.provider;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
public interface StreamServerDataProvider<T extends Accessor<?>, D> extends IServerDataProvider<T> {
@Override
default void appendServerData(CompoundTag data, T accessor) {
D value = streamData(accessor);
if (value != null) {
data.put(getUid().toString(), accessor.encodeAsNbt(streamCodec(), value));
}
}
@Nullable
D streamData(T accessor);
StreamCodec<RegistryFriendlyByteBuf, D> streamCodec();
}

View File

@@ -0,0 +1,34 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum BeehiveProvider implements StreamServerDataProvider<BlockAccessor, Byte> {
INSTANCE;
private static final ResourceLocation MC_BEEHIVE = JadeProtocol.mc_id("beehive");
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Byte> streamCodec() {
return ByteBufCodecs.BYTE.cast();
}
@Override
public Byte streamData(@NotNull BlockAccessor accessor) {
BeehiveBlockEntity beehive = (BeehiveBlockEntity) accessor.getBlockEntity();
int bees = beehive.getOccupantCount();
return (byte) (beehive.isFull() ? bees : -bees);
}
@Override
public ResourceLocation getUid() {
return MC_BEEHIVE;
}
}

View File

@@ -0,0 +1,60 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.state.properties.ChestType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum BlockNameProvider implements StreamServerDataProvider<BlockAccessor, Component> {
INSTANCE;
private static final ResourceLocation CORE_OBJECT_NAME = JadeProtocol.id("object_name");
@Override
@Nullable
public Component streamData(@NotNull BlockAccessor accessor) {
if (!(accessor.getBlockEntity() instanceof Nameable nameable)) {
return null;
}
if (nameable instanceof ChestBlockEntity && accessor.getBlock() instanceof ChestBlock && accessor.getBlockState().getValue(ChestBlock.TYPE) != ChestType.SINGLE) {
MenuProvider menuProvider = accessor.getBlockState().getMenuProvider(accessor.getLevel(), accessor.getPosition());
if (menuProvider != null) {
Component name = menuProvider.getDisplayName();
if (!(name.getContents() instanceof TranslatableContents contents) || !"container.chestDouble".equals(contents.getKey())) {
return name;
}
}
} else if (nameable.hasCustomName()) {
return nameable.getDisplayName();
}
return accessor.getBlockEntity().components().get(DataComponents.ITEM_NAME);
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, Component> streamCodec() {
return ComponentSerialization.STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return CORE_OBJECT_NAME;
}
@Override
public int getDefaultPriority() {
return -10100;
}
}

View File

@@ -0,0 +1,43 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum BrewingStandProvider implements StreamServerDataProvider<BlockAccessor, BrewingStandProvider.Data> {
INSTANCE;
private static final ResourceLocation MC_BREWING_STAND = JadeProtocol.mc_id("brewing_stand");
@Override
public @NotNull Data streamData(@NotNull BlockAccessor accessor) {
BrewingStandBlockEntity brewingStand = (BrewingStandBlockEntity) accessor.getBlockEntity();
return new Data(brewingStand.fuel, brewingStand.brewTime);
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Data> streamCodec() {
return Data.STREAM_CODEC.cast();
}
@Override
public ResourceLocation getUid() {
return MC_BREWING_STAND;
}
public record Data(int fuel, int time) {
public static final StreamCodec<ByteBuf, Data> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
Data::fuel,
ByteBufCodecs.VAR_INT,
Data::time,
Data::new);
}
}

View File

@@ -0,0 +1,55 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import com.google.common.collect.Lists;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.NbtOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.block.entity.CampfireBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider;
import org.leavesmc.leaves.protocol.jade.util.ViewGroup;
import java.util.List;
public enum CampfireProvider implements IServerExtensionProvider<ItemStack> {
INSTANCE;
private static final MapCodec<Integer> COOKING_TIME_CODEC = Codec.INT.fieldOf("jade:cooking");
private static final ResourceLocation MC_CAMPFIRE = JadeProtocol.mc_id("campfire");
@Override
public @Nullable @Unmodifiable List<ViewGroup<ItemStack>> getGroups(@NotNull Accessor<?> request) {
if (request.getTarget() instanceof CampfireBlockEntity campfire) {
List<ItemStack> list = Lists.newArrayList();
for (int i = 0; i < campfire.cookingTime.length; i++) {
ItemStack stack = campfire.getItems().get(i);
if (stack.isEmpty()) {
continue;
}
stack = stack.copy();
CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY)
.update(NbtOps.INSTANCE, COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i])
.getOrThrow();
stack.set(DataComponents.CUSTOM_DATA, customData);
list.add(stack);
}
return List.of(new ViewGroup<>(list));
}
return null;
}
@Override
public ResourceLocation getUid() {
return MC_CAMPFIRE;
}
}

View File

@@ -0,0 +1,44 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.ChiseledBookShelfBlock;
import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.ItemStorageProvider;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum ChiseledBookshelfProvider implements StreamServerDataProvider<BlockAccessor, ItemStack> {
INSTANCE;
private static final ResourceLocation MC_CHISELED_BOOKSHELF = JadeProtocol.mc_id("chiseled_bookshelf");
@Override
public @Nullable ItemStack streamData(@NotNull BlockAccessor accessor) {
int slot = ((ChiseledBookShelfBlock) accessor.getBlock()).getHitSlot(accessor.getHitResult(), accessor.getBlockState()).orElse(-1);
if (slot == -1) {
return null;
}
return ((ChiseledBookShelfBlockEntity) accessor.getBlockEntity()).getItem(slot);
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ItemStack> streamCodec() {
return ItemStack.OPTIONAL_STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_CHISELED_BOOKSHELF;
}
@Override
public int getDefaultPriority() {
return ItemStorageProvider.getBlock().getDefaultPriority() + 1;
}
}

View File

@@ -0,0 +1,40 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum CommandBlockProvider implements StreamServerDataProvider<BlockAccessor, String> {
INSTANCE;
private static final ResourceLocation MC_COMMAND_BLOCK = JadeProtocol.mc_id("command_block");
@Nullable
public String streamData(@NotNull BlockAccessor accessor) {
if (!accessor.getPlayer().canUseGameMasterBlocks()) {
return null;
}
String command = ((CommandBlockEntity) accessor.getBlockEntity()).getCommandBlock().getCommand();
if (command.length() > 40) {
command = command.substring(0, 37) + "...";
}
return command;
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, String> streamCodec() {
return ByteBufCodecs.STRING_UTF8.cast();
}
@Override
public ResourceLocation getUid() {
return MC_COMMAND_BLOCK;
}
}

View File

@@ -0,0 +1,51 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
import java.util.List;
public enum FurnaceProvider implements StreamServerDataProvider<BlockAccessor, FurnaceProvider.Data> {
INSTANCE;
private static final ResourceLocation MC_FURNACE = JadeProtocol.mc_id("furnace");
@Override
public @NotNull Data streamData(@NotNull BlockAccessor accessor) {
AbstractFurnaceBlockEntity furnace = (AbstractFurnaceBlockEntity) accessor.getBlockEntity();
return new Data(
furnace.cookingTimer,
furnace.cookingTotalTime,
List.of(furnace.getItem(0), furnace.getItem(1), furnace.getItem(2))
);
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, Data> streamCodec() {
return Data.STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_FURNACE;
}
public record Data(int progress, int total, List<ItemStack> inventory) {
public static final StreamCodec<RegistryFriendlyByteBuf, Data> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
Data::progress,
ByteBufCodecs.VAR_INT,
Data::total,
ItemStack.OPTIONAL_LIST_STREAM_CODEC,
Data::inventory,
Data::new);
}
}

View File

@@ -0,0 +1,37 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum HopperLockProvider implements StreamServerDataProvider<BlockAccessor, Boolean> {
INSTANCE;
private static final ResourceLocation MC_HOPPER_LOCK = JadeProtocol.mc_id("hopper_lock");
@Override
public Boolean streamData(@NotNull BlockAccessor accessor) {
return !accessor.getBlockState().getValue(BlockStateProperties.ENABLED);
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Boolean> streamCodec() {
return ByteBufCodecs.BOOL.cast();
}
@Override
public ResourceLocation getUid() {
return MC_HOPPER_LOCK;
}
@Override
public int getDefaultPriority() {
return BlockNameProvider.INSTANCE.getDefaultPriority() + 10;
}
}

View File

@@ -0,0 +1,32 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.JukeboxBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum JukeboxProvider implements StreamServerDataProvider<BlockAccessor, ItemStack> {
INSTANCE;
private static final ResourceLocation MC_JUKEBOX = JadeProtocol.mc_id("jukebox");
@Override
public @NotNull ItemStack streamData(BlockAccessor accessor) {
return ((JukeboxBlockEntity) accessor.getBlockEntity()).getTheItem();
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ItemStack> streamCodec() {
return ItemStack.OPTIONAL_STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_JUKEBOX;
}
}

View File

@@ -0,0 +1,33 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum LecternProvider implements StreamServerDataProvider<BlockAccessor, ItemStack> {
INSTANCE;
private static final ResourceLocation MC_LECTERN = JadeProtocol.mc_id("lectern");
@Override
public @NotNull ItemStack streamData(@NotNull BlockAccessor accessor) {
return ((LecternBlockEntity) accessor.getBlockEntity()).getBook();
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ItemStack> streamCodec() {
return ItemStack.OPTIONAL_STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_LECTERN;
}
}

View File

@@ -0,0 +1,42 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity;
import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerStateData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum MobSpawnerCooldownProvider implements StreamServerDataProvider<BlockAccessor, Integer> {
INSTANCE;
private static final ResourceLocation MC_MOB_SPAWNER_COOLDOWN = JadeProtocol.mc_id("mob_spawner.cooldown");
@Override
public @Nullable Integer streamData(@NotNull BlockAccessor accessor) {
TrialSpawnerBlockEntity spawner = (TrialSpawnerBlockEntity) accessor.getBlockEntity();
TrialSpawnerStateData spawnerData = spawner.getTrialSpawner().getStateData();
ServerLevel level = ((ServerLevel) accessor.getLevel());
if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) {
return (int) (spawnerData.cooldownEndsAt - level.getGameTime());
}
return null;
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Integer> streamCodec() {
return ByteBufCodecs.VAR_INT.cast();
}
@Override
public ResourceLocation getUid() {
return MC_MOB_SPAWNER_COOLDOWN;
}
}

View File

@@ -0,0 +1,36 @@
package org.leavesmc.leaves.protocol.jade.provider.block;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.CalibratedSculkSensorBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity;
import net.minecraft.world.level.block.entity.ComparatorBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
public enum RedstoneProvider implements IServerDataProvider<BlockAccessor> {
INSTANCE;
private static final ResourceLocation MC_REDSTONE = JadeProtocol.mc_id("redstone");
@Override
public void appendServerData(CompoundTag data, @NotNull BlockAccessor accessor) {
BlockEntity blockEntity = accessor.getBlockEntity();
if (blockEntity instanceof ComparatorBlockEntity comparator) {
data.putInt("Signal", comparator.getOutputSignal());
} else if (blockEntity instanceof CalibratedSculkSensorBlockEntity) {
Direction direction = accessor.getBlockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite();
int signal = accessor.getLevel().getSignal(accessor.getPosition().relative(direction), direction);
data.putInt("Signal", signal);
}
}
@Override
public ResourceLocation getUid() {
return MC_REDSTONE;
}
}

View File

@@ -0,0 +1,48 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityReference;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.OwnableEntity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
import org.leavesmc.leaves.protocol.jade.util.CommonUtil;
import java.util.UUID;
public enum AnimalOwnerProvider implements StreamServerDataProvider<EntityAccessor, String> {
INSTANCE;
private static final ResourceLocation MC_ANIMAL_OWNER = JadeProtocol.mc_id("animal_owner");
public static UUID getOwnerUUID(Entity entity) {
if (entity instanceof OwnableEntity ownableEntity) {
EntityReference<LivingEntity> reference = ownableEntity.getOwnerReference();
if (reference != null) {
return reference.getUUID();
}
}
return null;
}
@Override
public String streamData(@NotNull EntityAccessor accessor) {
return CommonUtil.getLastKnownUsername(getOwnerUUID(accessor.getEntity()));
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, String> streamCodec() {
return ByteBufCodecs.STRING_UTF8.cast();
}
@Override
public ResourceLocation getUid() {
return MC_ANIMAL_OWNER;
}
}

View File

@@ -0,0 +1,44 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.allay.Allay;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum MobBreedingProvider implements StreamServerDataProvider<EntityAccessor, Integer> {
INSTANCE;
private static final ResourceLocation MC_MOB_BREEDING = JadeProtocol.mc_id("mob_breeding");
@Override
public @Nullable Integer streamData(@NotNull EntityAccessor accessor) {
int time = 0;
Entity entity = accessor.getEntity();
if (entity instanceof Allay allay) {
if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) {
time = (int) allay.duplicationCooldown;
}
} else {
time = ((Animal) entity).getAge();
}
return time > 0 ? time : null;
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Integer> streamCodec() {
return ByteBufCodecs.VAR_INT.cast();
}
@Override
public ResourceLocation getUid() {
return MC_MOB_BREEDING;
}
}

View File

@@ -0,0 +1,43 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.frog.Tadpole;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum MobGrowthProvider implements StreamServerDataProvider<EntityAccessor, Integer> {
INSTANCE;
private static final ResourceLocation MC_MOB_GROWTH = JadeProtocol.mc_id("mob_growth");
@Override
public @Nullable Integer streamData(@NotNull EntityAccessor accessor) {
int time = -1;
Entity entity = accessor.getEntity();
if (entity instanceof AgeableMob ageable) {
time = -ageable.getAge();
} else if (entity instanceof Tadpole tadpole) {
time = tadpole.getTicksLeftUntilAdult();
}
return time > 0 ? time : null;
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Integer> streamCodec() {
return ByteBufCodecs.VAR_INT.cast();
}
@Override
public ResourceLocation getUid() {
return MC_MOB_GROWTH;
}
}

View File

@@ -0,0 +1,42 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.animal.Chicken;
import net.minecraft.world.entity.animal.armadillo.Armadillo;
import net.minecraft.world.entity.animal.sniffer.Sniffer;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
public enum NextEntityDropProvider implements IServerDataProvider<EntityAccessor> {
INSTANCE;
private static final ResourceLocation MC_NEXT_ENTITY_DROP = JadeProtocol.mc_id("next_entity_drop");
@Override
public void appendServerData(CompoundTag tag, @NotNull EntityAccessor accessor) {
int max = 24000 * 2;
if (accessor.getEntity() instanceof Chicken chicken) {
if (!chicken.isBaby() && chicken.eggTime < max) {
tag.putInt("NextEggIn", chicken.eggTime);
}
} else if (accessor.getEntity() instanceof Armadillo armadillo) {
if (!armadillo.isBaby() && armadillo.scuteTime < max) {
tag.putInt("NextScuteIn", armadillo.scuteTime);
}
} else if (accessor.getEntity() instanceof Sniffer sniffer) {
long time = sniffer.getBrain().getTimeUntilExpiry(MemoryModuleType.SNIFF_COOLDOWN);
if (time > 0 && time < max) {
tag.putInt("NextSniffIn", (int) time);
}
}
}
@Override
public ResourceLocation getUid() {
return MC_NEXT_ENTITY_DROP;
}
}

View File

@@ -0,0 +1,35 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum PetArmorProvider implements StreamServerDataProvider<EntityAccessor, ItemStack> {
INSTANCE;
private static final ResourceLocation MC_PET_ARMOR = JadeProtocol.mc_id("pet_armor");
@Nullable
@Override
public ItemStack streamData(@NotNull EntityAccessor accessor) {
ItemStack armor = ((Mob) accessor.getEntity()).getBodyArmorItem();
return armor.isEmpty() ? null : armor;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ItemStack> streamCodec() {
return ItemStack.OPTIONAL_STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_PET_ARMOR;
}
}

View File

@@ -0,0 +1,45 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.LivingEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
import java.util.List;
public enum StatusEffectsProvider implements StreamServerDataProvider<EntityAccessor, List<MobEffectInstance>> {
INSTANCE;
private static final StreamCodec<RegistryFriendlyByteBuf, List<MobEffectInstance>> STREAM_CODEC = ByteBufCodecs.<RegistryFriendlyByteBuf, MobEffectInstance>list()
.apply(MobEffectInstance.STREAM_CODEC);
private static final ResourceLocation MC_POTION_EFFECTS = JadeProtocol.mc_id("potion_effects");
@Override
@Nullable
public List<MobEffectInstance> streamData(@NotNull EntityAccessor accessor) {
List<MobEffectInstance> effects = ((LivingEntity) accessor.getEntity()).getActiveEffects()
.stream()
.filter(MobEffectInstance::isVisible)
.toList();
return effects.isEmpty() ? null : effects;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, List<MobEffectInstance>> streamCodec() {
return STREAM_CODEC;
}
@Override
public ResourceLocation getUid() {
return MC_POTION_EFFECTS;
}
}

View File

@@ -0,0 +1,34 @@
package org.leavesmc.leaves.protocol.jade.provider.entity;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.monster.ZombieVillager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider;
public enum ZombieVillagerProvider implements StreamServerDataProvider<EntityAccessor, Integer> {
INSTANCE;
private static final ResourceLocation MC_ZOMBIE_VILLAGER = JadeProtocol.mc_id("zombie_villager");
@Override
public @Nullable Integer streamData(@NotNull EntityAccessor accessor) {
int time = ((ZombieVillager) accessor.getEntity()).villagerConversionTime;
return time > 0 ? time : null;
}
@Override
public @NotNull StreamCodec<RegistryFriendlyByteBuf, Integer> streamCodec() {
return ByteBufCodecs.VAR_INT.cast();
}
@Override
public ResourceLocation getUid() {
return MC_ZOMBIE_VILLAGER;
}
}

View File

@@ -0,0 +1,37 @@
package org.leavesmc.leaves.protocol.jade.tool;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.Tool;
import net.minecraft.world.level.block.state.BlockState;
import java.util.List;
public class ShearsToolHandler {
private static final ShearsToolHandler INSTANCE = new ShearsToolHandler();
private final List<ItemStack> tools;
public ShearsToolHandler() {
this.tools = List.of(Items.SHEARS.getDefaultInstance());
}
public static ShearsToolHandler getInstance() {
return INSTANCE;
}
public ItemStack test(BlockState state) {
for (ItemStack toolItem : tools) {
if (toolItem.isCorrectToolForDrops(state)) {
return toolItem;
}
Tool tool = toolItem.get(DataComponents.TOOL);
if (tool != null && tool.getMiningSpeed(state) > tool.defaultMiningSpeed()) {
return toolItem;
}
}
return ItemStack.EMPTY;
}
}

View File

@@ -0,0 +1,72 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.mojang.authlib.GameProfile;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.level.block.entity.SkullBlockEntity;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class CommonUtil {
public static Entity wrapPartEntityParent(Entity target) {
if (target instanceof EnderDragonPart part) {
return part.parentMob;
}
return target;
}
public static Entity getPartEntity(Entity parent, int index) {
if (parent == null) {
return null;
}
if (index < 0) {
return parent;
}
if (parent instanceof EnderDragon dragon) {
EnderDragonPart[] parts = dragon.getSubEntities();
if (index < parts.length) {
return parts[index];
}
}
return parent;
}
@Nullable
public static String getLastKnownUsername(@Nullable UUID uuid) {
if (uuid == null) {
return null;
}
Optional<GameProfile> optional = SkullBlockEntity.fetchGameProfile(String.valueOf(uuid)).getNow(Optional.empty());
return optional.map(GameProfile::getName).orElse(null);
}
public static <T> Map.Entry<ResourceLocation, List<ViewGroup<T>>> getServerExtensionData(
Accessor<?> accessor,
WrappedHierarchyLookup<IServerExtensionProvider<T>> lookup) {
for (var provider : lookup.wrappedGet(accessor)) {
List<ViewGroup<T>> groups;
try {
groups = provider.getGroups(accessor);
} catch (Exception e) {
LeavesLogger.LOGGER.severe(e.toString());
continue;
}
if (groups != null) {
return Map.entry(provider.getUid(), groups);
}
}
return null;
}
}

View File

@@ -0,0 +1,138 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.minecraft.core.IdMapper;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
public class HierarchyLookup<T extends IJadeProvider> implements IHierarchyLookup<T> {
private final Class<?> baseClass;
private final Cache<Class<?>, List<T>> resultCache = CacheBuilder.newBuilder().build();
private final boolean singleton;
protected boolean idMapped;
@Nullable
protected IdMapper<T> idMapper;
private ListMultimap<Class<?>, T> objects = ArrayListMultimap.create();
public HierarchyLookup(Class<?> baseClass) {
this(baseClass, false);
}
public HierarchyLookup(Class<?> baseClass, boolean singleton) {
this.baseClass = baseClass;
this.singleton = singleton;
}
@Override
public void idMapped() {
this.idMapped = true;
}
@Override
@Nullable
public IdMapper<T> idMapper() {
return idMapper;
}
@Override
public void register(Class<?> clazz, T provider) {
Preconditions.checkArgument(isClassAcceptable(clazz), "Class %s is not acceptable", clazz);
Objects.requireNonNull(provider.getUid());
JadeProtocol.priorities.put(provider);
objects.put(clazz, provider);
}
@Override
public boolean isClassAcceptable(Class<?> clazz) {
return baseClass.isAssignableFrom(clazz);
}
@Override
public List<T> get(Class<?> clazz) {
try {
return resultCache.get(clazz, () -> {
List<T> list = Lists.newArrayList();
getInternal(clazz, list);
list = ImmutableList.sortedCopyOf(COMPARATOR, list);
if (singleton && !list.isEmpty()) {
return ImmutableList.of(list.getFirst());
}
return list;
});
} catch (ExecutionException e) {
LeavesLogger.LOGGER.warning("HierarchyLookup error", e);
}
return List.of();
}
private void getInternal(Class<?> clazz, List<T> list) {
if (clazz != baseClass && clazz != Object.class) {
getInternal(clazz.getSuperclass(), list);
}
list.addAll(objects.get(clazz));
}
@Override
public boolean isEmpty() {
return objects.isEmpty();
}
@Override
public Stream<Map.Entry<Class<?>, Collection<T>>> entries() {
return objects.asMap().entrySet().stream();
}
@Override
public void invalidate() {
resultCache.invalidateAll();
}
@Override
public void loadComplete(PriorityStore<ResourceLocation, IJadeProvider> priorityStore) {
objects.asMap().forEach((clazz, list) -> {
if (list.size() < 2) {
return;
}
Set<ResourceLocation> set = Sets.newHashSetWithExpectedSize(list.size());
for (T provider : list) {
if (set.contains(provider.getUid())) {
throw new IllegalStateException("Duplicate UID: %s for %s".formatted(provider.getUid(), list.stream()
.filter(p -> p.getUid().equals(provider.getUid()))
.map(p -> p.getClass().getName())
.toList()
));
}
set.add(provider.getUid());
}
});
objects = ImmutableListMultimap.<Class<?>, T>builder()
.orderValuesBy(Comparator.comparingInt(priorityStore::byValue))
.putAll(objects)
.build();
if (idMapped) {
idMapper = createIdMapper();
}
}
}

View File

@@ -0,0 +1,71 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.google.common.collect.Streams;
import net.minecraft.core.IdMapper;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
public interface IHierarchyLookup<T extends IJadeProvider> {
Comparator<IJadeProvider> COMPARATOR = Comparator.comparingInt(provider -> JadeProtocol.priorities.byValue(provider));
default IHierarchyLookup<? extends T> cast() {
return this;
}
void idMapped();
@Nullable
IdMapper<T> idMapper();
default List<ResourceLocation> mappedIds() {
return Streams.stream(Objects.requireNonNull(idMapper()))
.map(IJadeProvider::getUid)
.toList();
}
void register(Class<?> clazz, T provider);
boolean isClassAcceptable(Class<?> clazz);
default List<T> get(Object obj) {
if (obj == null) {
return List.of();
}
return get(obj.getClass());
}
List<T> get(Class<?> clazz);
boolean isEmpty();
Stream<Map.Entry<Class<?>, Collection<T>>> entries();
void invalidate();
void loadComplete(PriorityStore<ResourceLocation, IJadeProvider> priorityStore);
default IdMapper<T> createIdMapper() {
List<T> list = entries().flatMap(entry -> entry.getValue().stream()).toList();
IdMapper<T> idMapper = idMapper();
if (idMapper == null) {
idMapper = new IdMapper<>(list.size());
}
for (T provider : list) {
if (idMapper.getId(provider) == IdMapper.DEFAULT) {
idMapper.add(provider);
}
}
return idMapper;
}
}

View File

@@ -0,0 +1,121 @@
package org.leavesmc.leaves.protocol.jade.util;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.TooltipDisplay;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
public class ItemCollector<T> {
public static final int MAX_SIZE = 54;
public static final ItemCollector<?> EMPTY = new ItemCollector<>(null);
private static final Predicate<ItemStack> SHOWN = stack -> {
if (stack.isEmpty()) {
return false;
}
if (stack.getOrDefault(DataComponents.TOOLTIP_DISPLAY, TooltipDisplay.DEFAULT).hideTooltip()) {
return false;
}
if (stack.hasNonDefault(DataComponents.CUSTOM_MODEL_DATA) || stack.hasNonDefault(DataComponents.ITEM_MODEL)) {
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
for (String key : tag.keySet()) {
if (key.toLowerCase(Locale.ENGLISH).endsWith("clear") && tag.getBooleanOr(key, true)) {
return false;
}
}
}
return true;
};
private final Object2IntLinkedOpenHashMap<ItemDefinition> items = new Object2IntLinkedOpenHashMap<>();
private final ItemIterator<T> iterator;
public long version;
public long lastTimeFinished;
public boolean lastTimeIsEmpty;
public List<ViewGroup<ItemStack>> mergedResult;
public ItemCollector(ItemIterator<T> iterator) {
this.iterator = iterator;
}
public List<ViewGroup<ItemStack>> update(Accessor<?> request) {
if (iterator == null) {
return null;
}
T container = iterator.find(request.getTarget());
if (container == null) {
return null;
}
long currentVersion = iterator.getVersion(container);
long gameTime = request.getLevel().getGameTime();
if (mergedResult != null && iterator.isFinished()) {
if (version == currentVersion) {
return mergedResult; // content not changed
}
if (lastTimeFinished + 5 > gameTime) {
return mergedResult; // avoid update too frequently
}
iterator.reset();
}
AtomicInteger count = new AtomicInteger();
iterator.populate(container).forEach(stack -> {
count.incrementAndGet();
if (SHOWN.test(stack)) {
ItemDefinition def = new ItemDefinition(stack);
items.addTo(def, stack.getCount());
}
});
iterator.afterPopulate(count.get());
if (mergedResult != null && !iterator.isFinished()) {
updateCollectingProgress(mergedResult.getFirst());
return mergedResult;
}
List<ItemStack> partialResult = items.object2IntEntrySet().stream().limit(MAX_SIZE).map(entry -> {
ItemDefinition def = entry.getKey();
return def.toStack(entry.getIntValue());
}).toList();
List<ViewGroup<ItemStack>> groups = List.of(updateCollectingProgress(new ViewGroup<>(partialResult)));
if (iterator.isFinished()) {
mergedResult = groups;
lastTimeIsEmpty = mergedResult.getFirst().views.isEmpty();
version = currentVersion;
lastTimeFinished = gameTime;
items.clear();
}
return groups;
}
protected ViewGroup<ItemStack> updateCollectingProgress(ViewGroup<ItemStack> group) {
if (lastTimeIsEmpty && group.views.isEmpty()) {
return group;
}
float progress = iterator.getCollectingProgress();
CompoundTag data = group.getExtraData();
if (Float.isNaN(progress) || progress >= 1) {
data.remove("Collecting");
} else {
data.putFloat("Collecting", progress);
}
return group;
}
public record ItemDefinition(Item item, DataComponentPatch components) {
ItemDefinition(ItemStack stack) {
this(stack.getItem(), stack.getComponentsPatch());
}
public ItemStack toStack(int count) {
ItemStack itemStack = new ItemStack(item, count);
itemStack.applyComponents(components);
return itemStack;
}
}
}

View File

@@ -0,0 +1,102 @@
package org.leavesmc.leaves.protocol.jade.util;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public abstract class ItemIterator<T> {
public static final AtomicLong version = new AtomicLong();
protected final Function<Object, @Nullable T> containerFinder;
protected final int fromIndex;
protected boolean finished;
protected int currentIndex;
protected ItemIterator(Function<Object, @Nullable T> containerFinder, int fromIndex) {
this.containerFinder = containerFinder;
this.currentIndex = this.fromIndex = fromIndex;
}
public @Nullable T find(Object target) {
return containerFinder.apply(target);
}
public final boolean isFinished() {
return finished;
}
public long getVersion(T container) {
return version.getAndIncrement();
}
public abstract Stream<ItemStack> populate(T container);
public void reset() {
currentIndex = fromIndex;
finished = false;
}
public void afterPopulate(int count) {
currentIndex += count;
if (count == 0 || currentIndex >= 10000) {
finished = true;
}
}
public float getCollectingProgress() {
return Float.NaN;
}
public static abstract class SlottedItemIterator<T> extends ItemIterator<T> {
protected float progress;
public SlottedItemIterator(Function<Object, @Nullable T> containerFinder, int fromIndex) {
super(containerFinder, fromIndex);
}
protected abstract int getSlotCount(T container);
protected abstract ItemStack getItemInSlot(T container, int slot);
@Override
public Stream<ItemStack> populate(T container) {
int slotCount = getSlotCount(container);
int toIndex = currentIndex + ItemCollector.MAX_SIZE * 2;
if (toIndex >= slotCount) {
toIndex = slotCount;
finished = true;
}
progress = (float) (currentIndex - fromIndex) / (slotCount - fromIndex);
return IntStream.range(currentIndex, toIndex).mapToObj(slot -> getItemInSlot(container, slot));
}
@Override
public float getCollectingProgress() {
return progress;
}
}
public static class ContainerItemIterator extends SlottedItemIterator<Container> {
public ContainerItemIterator(int fromIndex) {
this(Container.class::cast, fromIndex);
}
public ContainerItemIterator(Function<Object, @Nullable Container> containerFinder, int fromIndex) {
super(containerFinder, fromIndex);
}
@Override
protected int getSlotCount(Container container) {
return container.getContainerSize();
}
@Override
protected ItemStack getItemInSlot(Container container, int slot) {
return container.getItem(slot);
}
}
}

View File

@@ -0,0 +1,59 @@
package org.leavesmc.leaves.protocol.jade.util;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import org.jetbrains.annotations.NotNull;
public class JadeCodec {
public static final StreamCodec<ByteBuf, Object> PRIMITIVE_STREAM_CODEC = new StreamCodec<>() {
@Override
public @NotNull Object decode(@NotNull ByteBuf buf) {
byte b = buf.readByte();
if (b == 0) {
return false;
} else if (b == 1) {
return true;
} else if (b == 2) {
return ByteBufCodecs.VAR_INT.decode(buf);
} else if (b == 3) {
return ByteBufCodecs.FLOAT.decode(buf);
} else if (b == 4) {
return ByteBufCodecs.STRING_UTF8.decode(buf);
} else if (b > 20) {
return b - 20;
}
throw new IllegalArgumentException("Unknown primitive type: " + b);
}
@Override
public void encode(@NotNull ByteBuf buf, @NotNull Object o) {
switch (o) {
case Boolean b -> buf.writeByte(b ? 1 : 0);
case Number n -> {
float f = n.floatValue();
if (f != (int) f) {
buf.writeByte(3);
ByteBufCodecs.FLOAT.encode(buf, f);
}
int i = n.intValue();
if (i <= Byte.MAX_VALUE - 20 && i >= 0) {
buf.writeByte(i + 20);
} else {
ByteBufCodecs.VAR_INT.encode(buf, i);
}
}
case String s -> {
buf.writeByte(4);
ByteBufCodecs.STRING_UTF8.encode(buf, s);
}
case Enum<?> anEnum -> {
buf.writeByte(4);
ByteBufCodecs.STRING_UTF8.encode(buf, anEnum.name());
}
case null -> throw new NullPointerException();
default -> throw new IllegalArgumentException("Unknown primitive type: %s (%s)".formatted(o, o.getClass()));
}
}
};
}

View File

@@ -0,0 +1,109 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.google.common.collect.Lists;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.entries.AlternativesEntry;
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
import net.minecraft.world.level.storage.loot.entries.NestedLootTable;
import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.MatchTool;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
public class LootTableMineableCollector {
private final HolderGetter<LootTable> lootRegistry;
private final ItemStack toolItem;
public LootTableMineableCollector(HolderGetter<LootTable> lootRegistry, ItemStack toolItem) {
this.lootRegistry = lootRegistry;
this.toolItem = toolItem;
}
public static @NotNull List<Block> execute(HolderGetter<LootTable> lootRegistry, ItemStack toolItem) {
LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem);
List<Block> list = Lists.newArrayList();
for (Block block : BuiltInRegistries.BLOCK) {
if (!ShearsToolHandler.getInstance().test(block.defaultBlockState()).isEmpty()) {
continue;
}
if (block.getLootTable().isPresent()) {
LootTable lootTable = lootRegistry.get(block.getLootTable().get()).map(Holder::value).orElse(null);
if (collector.doLootTable(lootTable)) {
list.add(block);
}
}
}
return list;
}
public static boolean isCorrectConditions(@NotNull List<LootItemCondition> conditions, ItemStack toolItem) {
if (conditions.size() != 1) {
return false;
}
LootItemCondition condition = conditions.getFirst();
if (condition instanceof MatchTool(Optional<ItemPredicate> predicate)) {
ItemPredicate itemPredicate = predicate.orElse(null);
return itemPredicate != null && itemPredicate.test(toolItem);
} else if (condition instanceof AnyOfCondition anyOfCondition) {
for (LootItemCondition child : anyOfCondition.terms) {
if (isCorrectConditions(List.of(child), toolItem)) {
return true;
}
}
}
return false;
}
private boolean doLootTable(LootTable lootTable) {
if (lootTable == null || lootTable == LootTable.EMPTY) {
return false;
}
for (LootPool pool : lootTable.pools) {
if (doLootPool(pool)) {
return true;
}
}
return false;
}
private boolean doLootPool(@NotNull LootPool lootPool) {
for (LootPoolEntryContainer entry : lootPool.entries) {
if (doLootPoolEntry(entry)) {
return true;
}
}
return false;
}
private boolean doLootPoolEntry(LootPoolEntryContainer entry) {
if (entry instanceof AlternativesEntry alternativesEntry) {
for (LootPoolEntryContainer child : alternativesEntry.children) {
if (doLootPoolEntry(child)) {
return true;
}
}
} else if (entry instanceof NestedLootTable nestedLootTable) {
LootTable lootTable = nestedLootTable.contents.map($ -> lootRegistry.get($).map(Holder::value).orElse(null), Function.identity());
return doLootTable(lootTable);
} else {
return isCorrectConditions(entry.conditions, toolItem);
}
return false;
}
}

View File

@@ -0,0 +1,115 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import net.minecraft.core.IdMapper;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
public class PairHierarchyLookup<T extends IJadeProvider> implements IHierarchyLookup<T> {
public final IHierarchyLookup<T> first;
public final IHierarchyLookup<T> second;
private final Cache<Pair<Class<?>, Class<?>>, List<T>> mergedCache = CacheBuilder.newBuilder().build();
protected boolean idMapped;
@Nullable
protected IdMapper<T> idMapper;
public PairHierarchyLookup(IHierarchyLookup<T> first, IHierarchyLookup<T> second) {
this.first = first;
this.second = second;
}
@SuppressWarnings("unchecked")
public <ANY> List<ANY> getMerged(Object first, Object second) {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
try {
return (List<ANY>) mergedCache.get(Pair.of(first.getClass(), second.getClass()), () -> {
List<T> firstList = this.first.get(first);
List<T> secondList = this.second.get(second);
if (firstList.isEmpty()) {
return secondList;
} else if (secondList.isEmpty()) {
return firstList;
}
return ImmutableList.sortedCopyOf(COMPARATOR, Iterables.concat(firstList, secondList));
});
} catch (ExecutionException e) {
LeavesLogger.LOGGER.severe(e.toString());
}
return List.of();
}
@Override
public void idMapped() {
idMapped = true;
}
@Override
public @Nullable IdMapper<T> idMapper() {
return idMapper;
}
@Override
public void register(Class<?> clazz, T provider) {
if (first.isClassAcceptable(clazz)) {
first.register(clazz, provider);
} else if (second.isClassAcceptable(clazz)) {
second.register(clazz, provider);
} else {
throw new IllegalArgumentException("Class " + clazz + " is not acceptable");
}
}
@Override
public boolean isClassAcceptable(Class<?> clazz) {
return first.isClassAcceptable(clazz) || second.isClassAcceptable(clazz);
}
@Override
public List<T> get(Class<?> clazz) {
List<T> result = first.get(clazz);
if (result.isEmpty()) {
result = second.get(clazz);
}
return result;
}
@Override
public boolean isEmpty() {
return first.isEmpty() && second.isEmpty();
}
@Override
public Stream<Map.Entry<Class<?>, Collection<T>>> entries() {
return Stream.concat(first.entries(), second.entries());
}
@Override
public void invalidate() {
first.invalidate();
second.invalidate();
mergedCache.invalidateAll();
}
@Override
public void loadComplete(PriorityStore<ResourceLocation, IJadeProvider> priorityStore) {
first.loadComplete(priorityStore);
second.loadComplete(priorityStore);
if (idMapped) {
idMapper = createIdMapper();
}
}
}

View File

@@ -0,0 +1,40 @@
package org.leavesmc.leaves.protocol.jade.util;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.ToIntFunction;
public class PriorityStore<K, V> {
private final Object2IntMap<K> priorities = new Object2IntLinkedOpenHashMap<>();
private final Function<V, K> keyGetter;
private final ToIntFunction<V> defaultPriorityGetter;
public PriorityStore(ToIntFunction<V> defaultPriorityGetter, Function<V, K> keyGetter) {
this.defaultPriorityGetter = defaultPriorityGetter;
this.keyGetter = keyGetter;
}
public void put(V provider) {
Objects.requireNonNull(provider);
put(provider, defaultPriorityGetter.applyAsInt(provider));
}
public void put(V provider, int priority) {
Objects.requireNonNull(provider);
K uid = keyGetter.apply(provider);
Objects.requireNonNull(uid);
priorities.put(uid, priority);
}
public int byValue(V value) {
return byKey(keyGetter.apply(value));
}
public int byKey(K id) {
return priorities.getInt(id);
}
}

View File

@@ -0,0 +1,58 @@
package org.leavesmc.leaves.protocol.jade.util;
import io.netty.buffer.ByteBuf;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ViewGroup<T> {
public List<T> views;
@Nullable
public String id;
@Nullable
protected CompoundTag extraData;
public ViewGroup(List<T> views) {
this(views, Optional.empty(), Optional.empty());
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public ViewGroup(List<T> views, Optional<String> id, Optional<CompoundTag> extraData) {
this.views = views;
this.id = id.orElse(null);
this.extraData = extraData.orElse(null);
}
public static <B extends ByteBuf, T> StreamCodec<B, ViewGroup<T>> codec(StreamCodec<B, T> viewCodec) {
return StreamCodec.composite(
ByteBufCodecs.<B, T>list().apply(viewCodec),
$ -> $.views,
ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8),
$ -> Optional.ofNullable($.id),
ByteBufCodecs.optional(ByteBufCodecs.COMPOUND_TAG),
$ -> Optional.ofNullable($.extraData),
ViewGroup::new);
}
public static <B extends ByteBuf, T> StreamCodec<B, Map.Entry<ResourceLocation, List<ViewGroup<T>>>> listCodec(StreamCodec<B, T> viewCodec) {
return StreamCodec.composite(
ResourceLocation.STREAM_CODEC,
Map.Entry::getKey,
ByteBufCodecs.<B, ViewGroup<T>>list().apply(codec(viewCodec)),
Map.Entry::getValue,
Map::entry);
}
public CompoundTag getExtraData() {
if (extraData == null) {
extraData = new CompoundTag();
}
return extraData;
}
}

View File

@@ -0,0 +1,107 @@
package org.leavesmc.leaves.protocol.jade.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
public class WrappedHierarchyLookup<T extends IJadeProvider> extends HierarchyLookup<T> {
public final List<Pair<IHierarchyLookup<T>, Function<Accessor<?>, @Nullable Object>>> overrides = Lists.newArrayList();
private boolean empty = true;
public WrappedHierarchyLookup() {
super(Object.class);
}
@NotNull
public static <T extends IJadeProvider> WrappedHierarchyLookup<T> forAccessor() {
WrappedHierarchyLookup<T> lookup = new WrappedHierarchyLookup<>();
lookup.overrides.add(Pair.of(
new HierarchyLookup<>(Block.class), accessor -> {
if (accessor instanceof BlockAccessor blockAccessor) {
return blockAccessor.getBlock();
}
return null;
}));
return lookup;
}
public List<T> wrappedGet(Accessor<?> accessor) {
Set<T> set = Sets.newLinkedHashSet();
for (var override : overrides) {
Object o = override.getRight().apply(accessor);
if (o != null) {
set.addAll(override.getLeft().get(o));
}
}
set.addAll(get(accessor.getTarget()));
return ImmutableList.sortedCopyOf(COMPARATOR, set);
}
@Override
public void register(Class<?> clazz, T provider) {
for (var override : overrides) {
if (override.getLeft().isClassAcceptable(clazz)) {
override.getLeft().register(clazz, provider);
empty = false;
return;
}
}
super.register(clazz, provider);
empty = false;
}
@Override
public boolean isClassAcceptable(Class<?> clazz) {
for (var override : overrides) {
if (override.getLeft().isClassAcceptable(clazz)) {
return true;
}
}
return super.isClassAcceptable(clazz);
}
@Override
public void invalidate() {
for (var override : overrides) {
override.getLeft().invalidate();
}
super.invalidate();
}
@Override
public void loadComplete(PriorityStore<ResourceLocation, IJadeProvider> priorityStore) {
for (var override : overrides) {
override.getLeft().loadComplete(priorityStore);
}
super.loadComplete(priorityStore);
}
@Override
public boolean isEmpty() {
return empty;
}
@Override
public Stream<Map.Entry<Class<?>, Collection<T>>> entries() {
Stream<Map.Entry<Class<?>, Collection<T>>> stream = super.entries();
for (var override : overrides) {
stream = Stream.concat(stream, override.getLeft().entries());
}
return stream;
}
}

View File

@@ -0,0 +1,389 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.mojang.authlib.GameProfile;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import org.bxteam.divinemc.config.DivineConfig;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.syncmatica.exchange.DownloadExchange;
import org.leavesmc.leaves.protocol.syncmatica.exchange.Exchange;
import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget;
import org.leavesmc.leaves.protocol.syncmatica.exchange.ModifyExchangeServer;
import org.leavesmc.leaves.protocol.syncmatica.exchange.UploadExchange;
import org.leavesmc.leaves.protocol.syncmatica.exchange.VersionHandshakeServer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@LeavesProtocol.Register(namespace = "syncmatica")
public class CommunicationManager implements LeavesProtocol {
protected static final Collection<ExchangeTarget> broadcastTargets = new ArrayList<>();
protected static final Map<UUID, Boolean> downloadState = new HashMap<>();
protected static final Map<UUID, Exchange> modifyState = new HashMap<>();
protected static final Rotation[] rotOrdinals = Rotation.values();
protected static final Mirror[] mirOrdinals = Mirror.values();
private static final Map<UUID, List<ServerPlacement>> downloadingFile = new HashMap<>();
private static final Map<ExchangeTarget, ServerPlayer> playerMap = new HashMap<>();
public CommunicationManager() {
}
public static GameProfile getGameProfile(final ExchangeTarget exchangeTarget) {
return playerMap.get(exchangeTarget).getGameProfile();
}
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
final ExchangeTarget newPlayer = player.connection.exchangeTarget;
final VersionHandshakeServer hi = new VersionHandshakeServer(newPlayer);
playerMap.put(newPlayer, player);
final GameProfile profile = player.getGameProfile();
SyncmaticaProtocol.getPlayerIdentifierProvider().updateName(profile.getId(), profile.getName());
startExchangeUnchecked(hi);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLeave(ServerPlayer player) {
final ExchangeTarget oldPlayer = player.connection.exchangeTarget;
final Collection<Exchange> potentialMessageTarget = oldPlayer.getExchanges();
if (potentialMessageTarget != null) {
for (final Exchange target : potentialMessageTarget) {
target.close(false);
handleExchange(target);
}
}
broadcastTargets.remove(oldPlayer);
playerMap.remove(oldPlayer);
}
@ProtocolHandler.PayloadReceiver(payload = SyncmaticaPayload.class)
public static void onPacketGet(ServerPlayer player, SyncmaticaPayload payload) {
onPacket(player.connection.exchangeTarget, payload.packetType(), payload.data());
}
public static void onPacket(final @NotNull ExchangeTarget source, final ResourceLocation id, final FriendlyByteBuf packetBuf) {
Exchange handler = null;
final Collection<Exchange> potentialMessageTarget = source.getExchanges();
if (potentialMessageTarget != null) {
for (final Exchange target : potentialMessageTarget) {
if (target.checkPacket(id, packetBuf)) {
target.handle(id, packetBuf);
handler = target;
break;
}
}
}
if (handler == null) {
handle(source, id, packetBuf);
} else if (handler.isFinished()) {
notifyClose(handler);
}
}
protected static void handle(ExchangeTarget source, @NotNull ResourceLocation id, FriendlyByteBuf packetBuf) {
if (id.equals(PacketType.REQUEST_LITEMATIC.identifier)) {
final UUID syncmaticaId = packetBuf.readUUID();
final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(syncmaticaId);
if (placement == null) {
return;
}
final File toUpload = SyncmaticaProtocol.getFileStorage().getLocalLitematic(placement);
final UploadExchange upload;
try {
upload = new UploadExchange(placement, toUpload, source);
} catch (final FileNotFoundException e) {
e.printStackTrace();
return;
}
startExchange(upload);
return;
}
if (id.equals(PacketType.REGISTER_METADATA.identifier)) {
final ServerPlacement placement = receiveMetaData(packetBuf, source);
if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) {
cancelShare(source, placement);
return;
}
final GameProfile profile = playerMap.get(source).getGameProfile();
final PlayerIdentifier playerIdentifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(profile);
if (!placement.getOwner().equals(playerIdentifier)) {
placement.setOwner(playerIdentifier);
placement.setLastModifiedBy(playerIdentifier);
}
if (!SyncmaticaProtocol.getFileStorage().getLocalState(placement).isLocalFileReady()) {
if (SyncmaticaProtocol.getFileStorage().getLocalState(placement) == LocalLitematicState.DOWNLOADING_LITEMATIC) {
downloadingFile.computeIfAbsent(placement.getHash(), key -> new ArrayList<>()).add(placement);
return;
}
try {
download(placement, source);
} catch (final Exception e) {
e.printStackTrace();
}
return;
}
addPlacement(source, placement);
return;
}
if (id.equals(PacketType.REMOVE_SYNCMATIC.identifier)) {
final UUID placementId = packetBuf.readUUID();
final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId);
if (placement != null) {
if (!getGameProfile(source).getId().equals(placement.getOwner().uuid)) {
return;
}
final Exchange modifier = getModifier(placement);
if (modifier != null) {
modifier.close(true);
notifyClose(modifier);
}
SyncmaticaProtocol.getSyncmaticManager().removePlacement(placement);
for (final ExchangeTarget client : broadcastTargets) {
final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer());
newPacketBuf.writeUUID(placement.getId());
client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, newPacketBuf);
}
}
}
if (id.equals(PacketType.MODIFY_REQUEST.identifier)) {
final UUID placementId = packetBuf.readUUID();
final ModifyExchangeServer modifier = new ModifyExchangeServer(placementId, source);
startExchange(modifier);
}
}
protected static void handleExchange(Exchange exchange) {
if (exchange instanceof DownloadExchange) {
final ServerPlacement p = ((DownloadExchange) exchange).getPlacement();
if (exchange.isSuccessful()) {
addPlacement(exchange.getPartner(), p);
if (downloadingFile.containsKey(p.getHash())) {
for (final ServerPlacement placement : downloadingFile.get(p.getHash())) {
addPlacement(exchange.getPartner(), placement);
}
}
} else {
cancelShare(exchange.getPartner(), p);
if (downloadingFile.containsKey(p.getHash())) {
for (final ServerPlacement placement : downloadingFile.get(p.getHash())) {
cancelShare(exchange.getPartner(), placement);
}
}
}
downloadingFile.remove(p.getHash());
return;
}
if (exchange instanceof VersionHandshakeServer && exchange.isSuccessful()) {
broadcastTargets.add(exchange.getPartner());
}
if (exchange instanceof ModifyExchangeServer && exchange.isSuccessful()) {
final ServerPlacement placement = ((ModifyExchangeServer) exchange).getPlacement();
for (final ExchangeTarget client : broadcastTargets) {
if (client.getFeatureSet().hasFeature(Feature.MODIFY)) {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeUUID(placement.getId());
putPositionData(placement, buf, client);
if (client.getFeatureSet().hasFeature(Feature.CORE_EX)) {
buf.writeUUID(placement.getLastModifiedBy().uuid);
buf.writeUtf(placement.getLastModifiedBy().getName());
}
client.sendPacket(PacketType.MODIFY.identifier, buf);
} else {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeUUID(placement.getId());
client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, buf);
sendMetaData(placement, client);
}
}
}
}
private static void addPlacement(final ExchangeTarget t, final @NotNull ServerPlacement placement) {
if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) {
cancelShare(t, placement);
return;
}
SyncmaticaProtocol.getSyncmaticManager().addPlacement(placement);
for (final ExchangeTarget target : broadcastTargets) {
sendMetaData(placement, target);
}
}
private static void cancelShare(final @NotNull ExchangeTarget source, final @NotNull ServerPlacement placement) {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(placement.getId());
source.sendPacket(PacketType.CANCEL_SHARE.identifier, FriendlyByteBuf);
}
public static void sendMetaData(final ServerPlacement metaData, final ExchangeTarget target) {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
putMetaData(metaData, buf, target);
target.sendPacket(PacketType.REGISTER_METADATA.identifier, buf);
}
public static void putMetaData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) {
buf.writeUUID(metaData.getId());
buf.writeUtf(SyncmaticaProtocol.sanitizeFileName(metaData.getName()));
buf.writeUUID(metaData.getHash());
if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) {
buf.writeUUID(metaData.getOwner().uuid);
buf.writeUtf(metaData.getOwner().getName());
buf.writeUUID(metaData.getLastModifiedBy().uuid);
buf.writeUtf(metaData.getLastModifiedBy().getName());
}
putPositionData(metaData, buf, exchangeTarget);
}
public static void putPositionData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) {
buf.writeBlockPos(metaData.getPosition());
buf.writeUtf(metaData.getDimension());
buf.writeInt(metaData.getRotation().ordinal());
buf.writeInt(metaData.getMirror().ordinal());
if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) {
if (metaData.getSubRegionData().getModificationData() == null) {
buf.writeInt(0);
return;
}
final Collection<SubRegionPlacementModification> regionData = metaData.getSubRegionData().getModificationData().values();
buf.writeInt(regionData.size());
for (final SubRegionPlacementModification subPlacement : regionData) {
buf.writeUtf(subPlacement.name);
buf.writeBlockPos(subPlacement.position);
buf.writeInt(subPlacement.rotation.ordinal());
buf.writeInt(subPlacement.mirror.ordinal());
}
}
}
public static ServerPlacement receiveMetaData(final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) {
final UUID id = buf.readUUID();
final String fileName = SyncmaticaProtocol.sanitizeFileName(buf.readUtf(32767));
final UUID hash = buf.readUUID();
PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER;
PlayerIdentifier lastModifiedBy = PlayerIdentifier.MISSING_PLAYER;
if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) {
final PlayerIdentifierProvider provider = SyncmaticaProtocol.getPlayerIdentifierProvider();
owner = provider.createOrGet(buf.readUUID(), buf.readUtf(32767));
lastModifiedBy = provider.createOrGet(buf.readUUID(), buf.readUtf(32767));
}
final ServerPlacement placement = new ServerPlacement(id, fileName, hash, owner);
placement.setLastModifiedBy(lastModifiedBy);
receivePositionData(placement, buf, exchangeTarget);
return placement;
}
public static void receivePositionData(final @NotNull ServerPlacement placement, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) {
final BlockPos pos = buf.readBlockPos();
final String dimensionId = buf.readUtf(32767);
final Rotation rot = rotOrdinals[buf.readInt()];
final Mirror mir = mirOrdinals[buf.readInt()];
placement.move(dimensionId, pos, rot, mir);
if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) {
final SubRegionData subRegionData = placement.getSubRegionData();
subRegionData.reset();
final int limit = buf.readInt();
for (int i = 0; i < limit; i++) {
subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), rotOrdinals[buf.readInt()], mirOrdinals[buf.readInt()]);
}
}
}
public static void download(final ServerPlacement syncmatic, final ExchangeTarget source) throws NoSuchAlgorithmException, IOException {
if (!SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).isReadyForDownload()) {
throw new IllegalArgumentException(syncmatic.toString() + " is not ready for download local state is: " + SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).toString());
}
final File toDownload = SyncmaticaProtocol.getFileStorage().createLocalLitematic(syncmatic);
final Exchange downloadExchange = new DownloadExchange(syncmatic, toDownload, source);
setDownloadState(syncmatic, true);
startExchange(downloadExchange);
}
public static void setDownloadState(final @NotNull ServerPlacement syncmatic, final boolean b) {
downloadState.put(syncmatic.getHash(), b);
}
public static boolean getDownloadState(final @NotNull ServerPlacement syncmatic) {
return downloadState.getOrDefault(syncmatic.getHash(), false);
}
public static void setModifier(final @NotNull ServerPlacement syncmatic, final Exchange exchange) {
modifyState.put(syncmatic.getHash(), exchange);
}
public static Exchange getModifier(final @NotNull ServerPlacement syncmatic) {
return modifyState.get(syncmatic.getHash());
}
public static void startExchange(final @NotNull Exchange newExchange) {
if (!broadcastTargets.contains(newExchange.getPartner())) {
throw new IllegalArgumentException(newExchange.getPartner().toString() + " is not a valid ExchangeTarget");
}
startExchangeUnchecked(newExchange);
}
protected static void startExchangeUnchecked(final @NotNull Exchange newExchange) {
newExchange.getPartner().getExchanges().add(newExchange);
newExchange.init();
if (newExchange.isFinished()) {
notifyClose(newExchange);
}
}
public static void notifyClose(final @NotNull Exchange e) {
e.getPartner().getExchanges().remove(e);
handleExchange(e);
}
public void sendMessage(final @NotNull ExchangeTarget client, final MessageType type, final String identifier) {
if (client.getFeatureSet().hasFeature(Feature.MESSAGE)) {
final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer());
newPacketBuf.writeUtf(type.toString());
newPacketBuf.writeUtf(identifier);
client.sendPacket(PacketType.MESSAGE.identifier, newPacketBuf);
} else if (playerMap.containsKey(client)) {
final ServerPlayer player = playerMap.get(client);
player.sendSystemMessage(Component.literal("Syncmatica " + type.toString() + " " + identifier));
}
}
@Override
public boolean isActive() {
return DivineConfig.NetworkCategory.protocolsSyncMaticaEnabled;
}
}

View File

@@ -0,0 +1,23 @@
package org.leavesmc.leaves.protocol.syncmatica;
import org.jetbrains.annotations.Nullable;
public enum Feature {
CORE,
FEATURE,
MODIFY,
MESSAGE,
QUOTA,
DEBUG,
CORE_EX;
@Nullable
public static Feature fromString(final String s) {
for (final Feature f : Feature.values()) {
if (f.toString().equals(s)) {
return f;
}
}
return null;
}
}

View File

@@ -0,0 +1,68 @@
package org.leavesmc.leaves.protocol.syncmatica;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class FeatureSet {
private static final Map<String, FeatureSet> versionFeatures;
static {
versionFeatures = new HashMap<>();
versionFeatures.put("0.1", new FeatureSet(Collections.singletonList(Feature.CORE)));
}
private final Collection<Feature> features;
public FeatureSet(final Collection<Feature> features) {
this.features = features;
}
@Nullable
public static FeatureSet fromVersionString(@NotNull String version) {
if (version.matches("^\\d+(\\.\\d+){2,4}$")) {
final int minSize = version.indexOf(".");
while (version.length() > minSize) {
if (versionFeatures.containsKey(version)) {
return versionFeatures.get(version);
}
final int lastDot = version.lastIndexOf(".");
version = version.substring(0, lastDot);
}
}
return null;
}
@NotNull
public static FeatureSet fromString(final @NotNull String features) {
final FeatureSet featureSet = new FeatureSet(new ArrayList<>());
for (final String feature : features.split("\n")) {
final Feature f = Feature.fromString(feature);
if (f != null) {
featureSet.features.add(f);
}
}
return featureSet;
}
@Override
public String toString() {
final StringBuilder output = new StringBuilder();
boolean b = false;
for (final Feature feature : features) {
output.append(b ? "\n" + feature.toString() : feature.toString());
b = true;
}
return output.toString();
}
public boolean hasFeature(final Feature f) {
return features.contains(f);
}
}

View File

@@ -0,0 +1,80 @@
package org.leavesmc.leaves.protocol.syncmatica;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.UUID;
public class FileStorage {
private final HashMap<ServerPlacement, Long> buffer = new HashMap<>();
public LocalLitematicState getLocalState(final ServerPlacement placement) {
final File localFile = getSchematicPath(placement);
if (localFile.isFile()) {
if (isDownloading(placement)) {
return LocalLitematicState.DOWNLOADING_LITEMATIC;
}
if ((buffer.containsKey(placement) && buffer.get(placement) == localFile.lastModified()) || hashCompare(localFile, placement)) {
return LocalLitematicState.LOCAL_LITEMATIC_PRESENT;
}
return LocalLitematicState.LOCAL_LITEMATIC_DESYNC;
}
return LocalLitematicState.NO_LOCAL_LITEMATIC;
}
private boolean isDownloading(final ServerPlacement placement) {
return CommunicationManager.getDownloadState(placement);
}
public File getLocalLitematic(final ServerPlacement placement) {
if (getLocalState(placement).isLocalFileReady()) {
return getSchematicPath(placement);
} else {
return null;
}
}
public File createLocalLitematic(final ServerPlacement placement) {
if (getLocalState(placement).isLocalFileReady()) {
throw new IllegalArgumentException("");
}
final File file = getSchematicPath(placement);
if (file.exists()) {
file.delete();
}
try {
file.createNewFile();
} catch (final IOException e) {
e.printStackTrace();
}
return file;
}
private boolean hashCompare(final File localFile, final ServerPlacement placement) {
UUID hash = null;
try {
hash = SyncmaticaProtocol.createChecksum(new FileInputStream(localFile));
} catch (final Exception e) {
e.printStackTrace();
}
if (hash == null) {
return false;
}
if (hash.equals(placement.getHash())) {
buffer.put(placement, localFile.lastModified());
return true;
}
return false;
}
@Contract("_ -> new")
private @NotNull File getSchematicPath(final @NotNull ServerPlacement placement) {
return new File(SyncmaticaProtocol.getLitematicFolder(), placement.getHash().toString() + ".litematic");
}
}

View File

@@ -0,0 +1,24 @@
package org.leavesmc.leaves.protocol.syncmatica;
public enum LocalLitematicState {
NO_LOCAL_LITEMATIC(true, false),
LOCAL_LITEMATIC_DESYNC(true, false),
DOWNLOADING_LITEMATIC(false, false),
LOCAL_LITEMATIC_PRESENT(false, true);
private final boolean downloadReady;
private final boolean fileReady;
LocalLitematicState(final boolean downloadReady, final boolean fileReady) {
this.downloadReady = downloadReady;
this.fileReady = fileReady;
}
public boolean isReadyForDownload() {
return downloadReady;
}
public boolean isLocalFileReady() {
return fileReady;
}
}

View File

@@ -0,0 +1,8 @@
package org.leavesmc.leaves.protocol.syncmatica;
public enum MessageType {
SUCCESS,
INFO,
WARNING,
ERROR
}

View File

@@ -0,0 +1,30 @@
package org.leavesmc.leaves.protocol.syncmatica;
import net.minecraft.resources.ResourceLocation;
public enum PacketType {
REGISTER_METADATA("register_metadata"),
CANCEL_SHARE("cancel_share"),
REQUEST_LITEMATIC("request_download"),
SEND_LITEMATIC("send_litematic"),
RECEIVED_LITEMATIC("received_litematic"),
FINISHED_LITEMATIC("finished_litematic"),
CANCEL_LITEMATIC("cancel_litematic"),
REMOVE_SYNCMATIC("remove_syncmatic"),
REGISTER_VERSION("register_version"),
CONFIRM_USER("confirm_user"),
FEATURE_REQUEST("feature_request"),
FEATURE("feature"),
MODIFY("modify"),
MODIFY_REQUEST("modify_request"),
MODIFY_REQUEST_DENY("modify_request_deny"),
MODIFY_REQUEST_ACCEPT("modify_request_accept"),
MODIFY_FINISH("modify_finish"),
MESSAGE("mesage");
public final ResourceLocation identifier;
PacketType(final String id) {
identifier = ResourceLocation.tryBuild(SyncmaticaProtocol.PROTOCOL_ID, id);
}
}

View File

@@ -0,0 +1,37 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.UUID;
public class PlayerIdentifier {
public static final UUID MISSING_PLAYER_UUID = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea");
public static final PlayerIdentifier MISSING_PLAYER = new PlayerIdentifier(MISSING_PLAYER_UUID, "No Player");
public final UUID uuid;
private String bufferedPlayerName;
PlayerIdentifier(final UUID uuid, final String bufferedPlayerName) {
this.uuid = uuid;
this.bufferedPlayerName = bufferedPlayerName;
}
public String getName() {
return bufferedPlayerName;
}
public void updatePlayerName(final String name) {
bufferedPlayerName = name;
}
public JsonObject toJson() {
final JsonObject jsonObject = new JsonObject();
jsonObject.add("uuid", new JsonPrimitive(uuid.toString()));
jsonObject.add("name", new JsonPrimitive(bufferedPlayerName));
return jsonObject;
}
}

View File

@@ -0,0 +1,46 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PlayerIdentifierProvider {
private final Map<UUID, PlayerIdentifier> identifiers = new HashMap<>();
public PlayerIdentifierProvider() {
identifiers.put(PlayerIdentifier.MISSING_PLAYER_UUID, PlayerIdentifier.MISSING_PLAYER);
}
public PlayerIdentifier createOrGet(final ExchangeTarget exchangeTarget) {
return createOrGet(CommunicationManager.getGameProfile(exchangeTarget));
}
public PlayerIdentifier createOrGet(final @NotNull GameProfile gameProfile) {
return createOrGet(gameProfile.getId(), gameProfile.getName());
}
public PlayerIdentifier createOrGet(final UUID uuid, final String playerName) {
return identifiers.computeIfAbsent(uuid, id -> new PlayerIdentifier(uuid, playerName));
}
public void updateName(final UUID uuid, final String playerName) {
createOrGet(uuid, playerName).updatePlayerName(playerName);
}
public PlayerIdentifier fromJson(final @NotNull JsonObject obj) {
if (!obj.has("uuid") || !obj.has("name")) {
return PlayerIdentifier.MISSING_PLAYER;
}
final UUID jsonUUID = UUID.fromString(obj.get("uuid").getAsString());
return identifiers.computeIfAbsent(jsonUUID,
key -> new PlayerIdentifier(jsonUUID, obj.get("name").getAsString())
);
}
}

View File

@@ -0,0 +1,166 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class ServerPlacement {
private final UUID id;
private final String fileName;
private final UUID hashValue;
private PlayerIdentifier owner;
private PlayerIdentifier lastModifiedBy;
private ServerPosition origin;
private Rotation rotation;
private Mirror mirror;
private SubRegionData subRegionData = new SubRegionData();
public ServerPlacement(final UUID id, final String fileName, final UUID hashValue, final PlayerIdentifier owner) {
this.id = id;
this.fileName = fileName;
this.hashValue = hashValue;
this.owner = owner;
lastModifiedBy = owner;
}
@Nullable
public static ServerPlacement fromJson(final @NotNull JsonObject obj) {
if (obj.has("id")
&& obj.has("file_name")
&& obj.has("hash")
&& obj.has("origin")
&& obj.has("rotation")
&& obj.has("mirror")) {
final UUID id = UUID.fromString(obj.get("id").getAsString());
final String name = obj.get("file_name").getAsString();
final UUID hashValue = UUID.fromString(obj.get("hash").getAsString());
PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER;
if (obj.has("owner")) {
owner = SyncmaticaProtocol.getPlayerIdentifierProvider().fromJson(obj.get("owner").getAsJsonObject());
}
final ServerPlacement newPlacement = new ServerPlacement(id, name, hashValue, owner);
final ServerPosition pos = ServerPosition.fromJson(obj.get("origin").getAsJsonObject());
if (pos == null) {
return null;
}
newPlacement.origin = pos;
newPlacement.rotation = Rotation.valueOf(obj.get("rotation").getAsString());
newPlacement.mirror = Mirror.valueOf(obj.get("mirror").getAsString());
if (obj.has("lastModifiedBy")) {
newPlacement.lastModifiedBy = SyncmaticaProtocol.getPlayerIdentifierProvider()
.fromJson(obj.get("lastModifiedBy").getAsJsonObject());
} else {
newPlacement.lastModifiedBy = owner;
}
if (obj.has("subregionData")) {
newPlacement.subRegionData = SubRegionData.fromJson(obj.get("subregionData"));
}
return newPlacement;
}
return null;
}
public UUID getId() {
return id;
}
public String getName() {
return fileName;
}
public UUID getHash() {
return hashValue;
}
public String getDimension() {
return origin.getDimensionId();
}
public BlockPos getPosition() {
return origin.getBlockPosition();
}
public ServerPosition getOrigin() {
return origin;
}
public Rotation getRotation() {
return rotation;
}
public Mirror getMirror() {
return mirror;
}
public ServerPlacement move(final String dimensionId, final BlockPos origin, final Rotation rotation, final Mirror mirror) {
move(new ServerPosition(origin, dimensionId), rotation, mirror);
return this;
}
public ServerPlacement move(final ServerPosition origin, final Rotation rotation, final Mirror mirror) {
this.origin = origin;
this.rotation = rotation;
this.mirror = mirror;
return this;
}
public PlayerIdentifier getOwner() {
return owner;
}
public void setOwner(final PlayerIdentifier playerIdentifier) {
owner = playerIdentifier;
}
public PlayerIdentifier getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(final PlayerIdentifier lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public SubRegionData getSubRegionData() {
return subRegionData;
}
public JsonObject toJson() {
final JsonObject obj = new JsonObject();
obj.add("id", new JsonPrimitive(id.toString()));
obj.add("file_name", new JsonPrimitive(fileName));
obj.add("hash", new JsonPrimitive(hashValue.toString()));
obj.add("origin", origin.toJson());
obj.add("rotation", new JsonPrimitive(rotation.name()));
obj.add("mirror", new JsonPrimitive(mirror.name()));
obj.add("owner", owner.toJson());
if (!owner.equals(lastModifiedBy)) {
obj.add("lastModifiedBy", lastModifiedBy.toJson());
}
if (subRegionData.isModified()) {
obj.add("subregionData", subRegionData.toJson());
}
return obj;
}
}

View File

@@ -0,0 +1,51 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.BlockPos;
public class ServerPosition {
private final BlockPos position;
private final String dimensionId;
public ServerPosition(final BlockPos pos, final String dim) {
position = pos;
dimensionId = dim;
}
public static ServerPosition fromJson(final JsonObject obj) {
if (obj.has("position") && obj.has("dimension")) {
final int x;
final int y;
final int z;
final JsonArray arr = obj.get("position").getAsJsonArray();
x = arr.get(0).getAsInt();
y = arr.get(1).getAsInt();
z = arr.get(2).getAsInt();
final BlockPos pos = new BlockPos(x, y, z);
return new ServerPosition(pos, obj.get("dimension").getAsString());
}
return null;
}
public BlockPos getBlockPosition() {
return position;
}
public String getDimensionId() {
return dimensionId;
}
public JsonObject toJson() {
final JsonObject obj = new JsonObject();
final JsonArray arr = new JsonArray();
arr.add(new JsonPrimitive(position.getX()));
arr.add(new JsonPrimitive(position.getY()));
arr.add(new JsonPrimitive(position.getZ()));
obj.add("position", arr);
obj.add("dimension", new JsonPrimitive(dimensionId));
return obj;
}
}

View File

@@ -0,0 +1,90 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public class SubRegionData {
private boolean isModified;
private Map<String, SubRegionPlacementModification> modificationData; // is null when isModified is false
public SubRegionData() {
this(false, null);
}
public SubRegionData(final boolean isModified, final Map<String, SubRegionPlacementModification> modificationData) {
this.isModified = isModified;
this.modificationData = modificationData;
}
@NotNull
public static SubRegionData fromJson(final @NotNull JsonElement obj) {
final SubRegionData newSubRegionData = new SubRegionData();
newSubRegionData.isModified = true;
for (final JsonElement modification : obj.getAsJsonArray()) {
newSubRegionData.modify(SubRegionPlacementModification.fromJson(modification.getAsJsonObject()));
}
return newSubRegionData;
}
public void reset() {
isModified = false;
modificationData = null;
}
public void modify(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) {
modify(new SubRegionPlacementModification(name, position, rotation, mirror));
}
public void modify(final SubRegionPlacementModification subRegionPlacementModification) {
if (subRegionPlacementModification == null) {
return;
}
isModified = true;
if (modificationData == null) {
modificationData = new HashMap<>();
}
modificationData.put(subRegionPlacementModification.name, subRegionPlacementModification);
}
public boolean isModified() {
return isModified;
}
public Map<String, SubRegionPlacementModification> getModificationData() {
return modificationData;
}
public JsonElement toJson() {
return modificationDataToJson();
}
@NotNull
private JsonElement modificationDataToJson() {
final JsonArray arr = new JsonArray();
for (final Map.Entry<String, SubRegionPlacementModification> entry : modificationData.entrySet()) {
arr.add(entry.getValue().toJson());
}
return arr;
}
@Override
public String toString() {
if (!isModified) {
return "[]";
}
return modificationData == null ? "[ERROR:null]" : modificationData.toString();
}
}

View File

@@ -0,0 +1,65 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SubRegionPlacementModification {
public final String name;
public final BlockPos position;
public final Rotation rotation;
public final Mirror mirror;
SubRegionPlacementModification(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) {
this.name = name;
this.position = position;
this.rotation = rotation;
this.mirror = mirror;
}
@Nullable
public static SubRegionPlacementModification fromJson(final @NotNull JsonObject obj) {
if (!obj.has("name") || !obj.has("position") || !obj.has("rotation") || !obj.has("mirror")) {
return null;
}
final String name = obj.get("name").getAsString();
final JsonArray arr = obj.get("position").getAsJsonArray();
if (arr.size() != 3) {
return null;
}
final BlockPos position = new BlockPos(arr.get(0).getAsInt(), arr.get(1).getAsInt(), arr.get(2).getAsInt());
final Rotation rotation = Rotation.valueOf(obj.get("rotation").getAsString());
final Mirror mirror = Mirror.valueOf(obj.get("mirror").getAsString());
return new SubRegionPlacementModification(name, position, rotation, mirror);
}
public JsonObject toJson() {
final JsonObject obj = new JsonObject();
final JsonArray arr = new JsonArray();
arr.add(position.getX());
arr.add(position.getY());
arr.add(position.getZ());
obj.add("position", arr);
obj.add("name", new JsonPrimitive(name));
obj.add("rotation", new JsonPrimitive(rotation.name()));
obj.add("mirror", new JsonPrimitive(mirror.name()));
return obj;
}
@Override
public String toString() {
return String.format("[name=%s, position=%s, rotation=%s, mirror=%s]", name, position, rotation, mirror);
}
}

View File

@@ -0,0 +1,108 @@
package org.leavesmc.leaves.protocol.syncmatica;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class SyncmaticManager {
public static final String PLACEMENTS_JSON_KEY = "placements";
private final Map<UUID, ServerPlacement> schematics = new HashMap<>();
public void addPlacement(final ServerPlacement placement) {
schematics.put(placement.getId(), placement);
updateServerPlacement();
}
public ServerPlacement getPlacement(final UUID id) {
return schematics.get(id);
}
public Collection<ServerPlacement> getAll() {
return schematics.values();
}
public void removePlacement(final @NotNull ServerPlacement placement) {
schematics.remove(placement.getId());
updateServerPlacement();
}
public void updateServerPlacement() {
saveServer();
}
public void startup() {
loadServer();
}
private void saveServer() {
final JsonObject obj = new JsonObject();
final JsonArray arr = new JsonArray();
for (final ServerPlacement p : getAll()) {
arr.add(p.toJson());
}
obj.add(PLACEMENTS_JSON_KEY, arr);
final File backup = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.bak");
final File incoming = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.new");
final File current = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json");
try (final FileWriter writer = new FileWriter(incoming)) {
writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(obj));
} catch (final IOException e) {
e.printStackTrace();
return;
}
SyncmaticaProtocol.backupAndReplace(backup.toPath(), current.toPath(), incoming.toPath());
}
private void loadServer() {
final File f = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json");
if (f.exists() && f.isFile() && f.canRead()) {
JsonElement element = null;
try {
final JsonParser parser = new JsonParser();
final FileReader reader = new FileReader(f);
element = parser.parse(reader);
reader.close();
} catch (final Exception e) {
e.printStackTrace();
}
if (element == null) {
return;
}
try {
final JsonObject obj = element.getAsJsonObject();
if (obj == null || !obj.has(PLACEMENTS_JSON_KEY)) {
return;
}
final JsonArray arr = obj.getAsJsonArray(PLACEMENTS_JSON_KEY);
for (final JsonElement elem : arr) {
final ServerPlacement placement = ServerPlacement.fromJson(elem.getAsJsonObject());
if (placement != null) {
schematics.put(placement.getId(), placement);
}
}
} catch (final IllegalStateException | NullPointerException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.syncmatica;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
public record SyncmaticaPayload(ResourceLocation packetType, FriendlyByteBuf data) implements LeavesCustomPayload {
@ID
private static final ResourceLocation NETWORK_ID = ResourceLocation.tryBuild(SyncmaticaProtocol.PROTOCOL_ID, "main");
@Codec
private static final StreamCodec<FriendlyByteBuf, SyncmaticaPayload> CODEC = StreamCodec.of(
(buf, payload) -> buf.writeResourceLocation(payload.packetType()).writeBytes(payload.data()),
buf -> new SyncmaticaPayload(buf.readResourceLocation(), new FriendlyByteBuf(buf.readBytes(buf.readableBytes())))
);
}

View File

@@ -0,0 +1,127 @@
package org.leavesmc.leaves.protocol.syncmatica;
import org.bxteam.divinemc.config.DivineConfig;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;
public class SyncmaticaProtocol {
public static final String PROTOCOL_ID = "syncmatica";
public static final String PROTOCOL_VERSION = "leaves-syncmatica-1.1.0";
private static final File litematicFolder = new File("." + File.separator + "syncmatics");
private static final PlayerIdentifierProvider playerIdentifierProvider = new PlayerIdentifierProvider();
private static final CommunicationManager communicationManager = new CommunicationManager();
private static final FeatureSet featureSet = new FeatureSet(Arrays.asList(Feature.values()));
private static final SyncmaticManager syncmaticManager = new SyncmaticManager();
private static final FileStorage fileStorage = new FileStorage();
private static final int[] ILLEGAL_CHARS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47, 34, 60, 62, 124};
private static final String ILLEGAL_PATTERNS = "(^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$)|(^\\.\\.*$)";
private static boolean loaded = false;
public static File getLitematicFolder() {
return litematicFolder;
}
public static PlayerIdentifierProvider getPlayerIdentifierProvider() {
return playerIdentifierProvider;
}
public static CommunicationManager getCommunicationManager() {
return communicationManager;
}
public static FeatureSet getFeatureSet() {
return featureSet;
}
public static SyncmaticManager getSyncmaticManager() {
return syncmaticManager;
}
public static FileStorage getFileStorage() {
return fileStorage;
}
public static void init(boolean status) {
if (status && !loaded) {
litematicFolder.mkdirs();
syncmaticManager.startup();
loaded = true;
} else if (!status && loaded) {
syncmaticManager.updateServerPlacement();
loaded = false;
}
}
@NotNull
public static UUID createChecksum(final @NotNull InputStream fis) throws NoSuchAlgorithmException, IOException {
final byte[] buffer = new byte[4096];
final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
int numRead;
do {
numRead = fis.read(buffer);
if (numRead > 0) {
messageDigest.update(buffer, 0, numRead);
}
} while (numRead != -1);
fis.close();
return UUID.nameUUIDFromBytes(messageDigest.digest());
}
@NotNull
public static String sanitizeFileName(final @NotNull String badFileName) {
final StringBuilder sanitized = new StringBuilder();
final int len = badFileName.codePointCount(0, badFileName.length());
for (int i = 0; i < len; i++) {
final int c = badFileName.codePointAt(i);
if (Arrays.binarySearch(ILLEGAL_CHARS, c) < 0) {
sanitized.appendCodePoint(c);
if (sanitized.length() == 255) {
break;
}
}
}
return sanitized.toString().replaceAll(ILLEGAL_PATTERNS, "_");
}
public static boolean isOverQuota(int sent) {
return DivineConfig.NetworkCategory.protocolsSyncMaticaQuota && sent > DivineConfig.NetworkCategory.protocolsSyncMaticaQuotaLimit;
}
public static void backupAndReplace(final Path backup, final Path current, final Path incoming) {
if (!Files.exists(incoming)) {
return;
}
if (overwrite(backup, current, 2) && !overwrite(current, incoming, 4)) {
overwrite(current, backup, 8);
}
}
private static boolean overwrite(final Path backup, final Path current, final int tries) {
if (!Files.exists(current)) {
return true;
}
try {
Files.deleteIfExists(backup);
Files.move(current, backup);
} catch (final IOException exception) {
if (tries <= 0) {
return false;
}
return overwrite(backup, current, tries - 1);
}
return true;
}
}

View File

@@ -0,0 +1,66 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import net.minecraft.network.FriendlyByteBuf;
import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
import java.util.UUID;
public abstract class AbstractExchange implements Exchange {
private final ExchangeTarget partner;
private boolean success = false;
private boolean finished = false;
protected AbstractExchange(final ExchangeTarget partner) {
this.partner = partner;
}
protected static boolean checkUUID(final FriendlyByteBuf sourceBuf, final UUID targetId) {
final int r = sourceBuf.readerIndex();
final UUID sourceId = sourceBuf.readUUID();
sourceBuf.readerIndex(r);
return sourceId.equals(targetId);
}
@Override
public ExchangeTarget getPartner() {
return partner;
}
@Override
public boolean isFinished() {
return finished;
}
@Override
public boolean isSuccessful() {
return success;
}
@Override
public void close(final boolean notifyPartner) {
finished = true;
success = false;
onClose();
if (notifyPartner) {
sendCancelPacket();
}
}
public CommunicationManager getManager() {
return SyncmaticaProtocol.getCommunicationManager();
}
protected void sendCancelPacket() {
}
protected void onClose() {
}
protected void succeed() {
finished = true;
success = true;
onClose();
}
}

View File

@@ -0,0 +1,128 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager;
import org.leavesmc.leaves.protocol.syncmatica.MessageType;
import org.leavesmc.leaves.protocol.syncmatica.PacketType;
import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class DownloadExchange extends AbstractExchange {
private final ServerPlacement toDownload;
private final OutputStream outputStream;
private final MessageDigest md5;
private final File downloadFile;
private int bytesSent;
public DownloadExchange(final ServerPlacement syncmatic, final File downloadFile, final ExchangeTarget partner) throws IOException, NoSuchAlgorithmException {
super(partner);
this.downloadFile = downloadFile;
final OutputStream os = new FileOutputStream(downloadFile);
toDownload = syncmatic;
md5 = MessageDigest.getInstance("MD5");
outputStream = new DigestOutputStream(os, md5);
}
@Override
public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
if (id.equals(PacketType.SEND_LITEMATIC.identifier)
|| id.equals(PacketType.FINISHED_LITEMATIC.identifier)
|| id.equals(PacketType.CANCEL_LITEMATIC.identifier)) {
return checkUUID(packetBuf, toDownload.getId());
}
return false;
}
@Override
public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) {
packetBuf.readUUID();
if (id.equals(PacketType.SEND_LITEMATIC.identifier)) {
final int size = packetBuf.readInt();
bytesSent += size;
if (SyncmaticaProtocol.isOverQuota(bytesSent)) {
close(true);
SyncmaticaProtocol.getCommunicationManager().sendMessage(
getPartner(),
MessageType.ERROR,
"syncmatica.error.cancelled_transmit_exceed_quota"
);
return;
}
try {
packetBuf.readBytes(outputStream, size);
} catch (final IOException e) {
close(true);
e.printStackTrace();
return;
}
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toDownload.getId());
getPartner().sendPacket(PacketType.RECEIVED_LITEMATIC.identifier, FriendlyByteBuf);
return;
}
if (id.equals(PacketType.FINISHED_LITEMATIC.identifier)) {
try {
outputStream.flush();
} catch (final IOException e) {
close(false);
e.printStackTrace();
return;
}
final UUID downloadHash = UUID.nameUUIDFromBytes(md5.digest());
if (downloadHash.equals(toDownload.getHash())) {
succeed();
} else {
close(false);
}
return;
}
if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) {
close(false);
}
}
@Override
public void init() {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toDownload.getId());
getPartner().sendPacket(PacketType.REQUEST_LITEMATIC.identifier, FriendlyByteBuf);
}
@Override
protected void onClose() {
getManager();
CommunicationManager.setDownloadState(toDownload, false);
try {
outputStream.close();
} catch (final IOException e) {
e.printStackTrace();
}
if (!isSuccessful() && downloadFile.exists()) {
downloadFile.delete();
}
}
@Override
protected void sendCancelPacket() {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toDownload.getId());
getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf);
}
public ServerPlacement getPlacement() {
return toDownload;
}
}

View File

@@ -0,0 +1,20 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
public interface Exchange {
ExchangeTarget getPartner();
boolean checkPacket(ResourceLocation id, FriendlyByteBuf packetBuf);
void handle(ResourceLocation id, FriendlyByteBuf packetBuf);
boolean isFinished();
boolean isSuccessful();
void close(boolean notifyPartner);
void init();
}

View File

@@ -0,0 +1,39 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.syncmatica.FeatureSet;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaPayload;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ExchangeTarget {
private final List<Exchange> ongoingExchanges = new ArrayList<>();
private final ServerGamePacketListenerImpl client;
private FeatureSet features;
public ExchangeTarget(final ServerGamePacketListenerImpl client) {
this.client = client;
}
public void sendPacket(final ResourceLocation id, final FriendlyByteBuf packetBuf) {
ProtocolUtils.sendPayloadPacket(client.player, new SyncmaticaPayload(id, packetBuf));
}
public FeatureSet getFeatureSet() {
return features;
}
public void setFeatureSet(final FeatureSet f) {
features = f;
}
public Collection<Exchange> getExchanges() {
return ongoingExchanges;
}
}

View File

@@ -0,0 +1,48 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.FeatureSet;
import org.leavesmc.leaves.protocol.syncmatica.PacketType;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
public abstract class FeatureExchange extends AbstractExchange {
protected FeatureExchange(final ExchangeTarget partner) {
super(partner);
}
@Override
public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
return id.equals(PacketType.FEATURE_REQUEST.identifier)
|| id.equals(PacketType.FEATURE.identifier);
}
@Override
public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
if (id.equals(PacketType.FEATURE_REQUEST.identifier)) {
sendFeatures();
} else if (id.equals(PacketType.FEATURE.identifier)) {
final FeatureSet fs = FeatureSet.fromString(packetBuf.readUtf(32767));
getPartner().setFeatureSet(fs);
onFeatureSetReceive();
}
}
protected void onFeatureSetReceive() {
succeed();
}
public void requestFeatureSet() {
getPartner().sendPacket(PacketType.FEATURE_REQUEST.identifier, new FriendlyByteBuf(Unpooled.buffer()));
}
private void sendFeatures() {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
final FeatureSet fs = SyncmaticaProtocol.getFeatureSet();
buf.writeUtf(fs.toString(), 32767);
getPartner().sendPacket(PacketType.FEATURE.identifier, buf);
}
}

View File

@@ -0,0 +1,82 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager;
import org.leavesmc.leaves.protocol.syncmatica.PacketType;
import org.leavesmc.leaves.protocol.syncmatica.PlayerIdentifier;
import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
import java.util.UUID;
public class ModifyExchangeServer extends AbstractExchange {
private final ServerPlacement placement;
UUID placementId;
public ModifyExchangeServer(final UUID placeId, final ExchangeTarget partner) {
super(partner);
placementId = placeId;
placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId);
}
@Override
public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
return id.equals(PacketType.MODIFY_FINISH.identifier) && checkUUID(packetBuf, placement.getId());
}
@Override
public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) {
packetBuf.readUUID();
if (id.equals(PacketType.MODIFY_FINISH.identifier)) {
CommunicationManager.receivePositionData(placement, packetBuf, getPartner());
final PlayerIdentifier identifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(
getPartner()
);
placement.setLastModifiedBy(identifier);
SyncmaticaProtocol.getSyncmaticManager().updateServerPlacement();
succeed();
}
}
@Override
public void init() {
if (getPlacement() == null || CommunicationManager.getModifier(placement) != null) {
close(true);
} else {
if (SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(this.getPartner()).uuid.equals(placement.getOwner().uuid)) {
accept();
} else {
close(true);
}
}
}
private void accept() {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeUUID(placement.getId());
getPartner().sendPacket(PacketType.MODIFY_REQUEST_ACCEPT.identifier, buf);
CommunicationManager.setModifier(placement, this);
}
@Override
protected void sendCancelPacket() {
final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeUUID(placementId);
getPartner().sendPacket(PacketType.MODIFY_REQUEST_DENY.identifier, buf);
}
public ServerPlacement getPlacement() {
return placement;
}
@Override
protected void onClose() {
if (CommunicationManager.getModifier(placement) == this) {
CommunicationManager.setModifier(placement, null);
}
}
}

View File

@@ -0,0 +1,101 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.PacketType;
import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class UploadExchange extends AbstractExchange {
private static final int BUFFER_SIZE = 16384;
private final ServerPlacement toUpload;
private final InputStream inputStream;
private final byte[] buffer = new byte[BUFFER_SIZE];
public UploadExchange(final ServerPlacement syncmatic, final File uploadFile, final ExchangeTarget partner) throws FileNotFoundException {
super(partner);
toUpload = syncmatic;
inputStream = new FileInputStream(uploadFile);
}
@Override
public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier)
|| id.equals(PacketType.CANCEL_LITEMATIC.identifier)) {
return checkUUID(packetBuf, toUpload.getId());
}
return false;
}
@Override
public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) {
packetBuf.readUUID();
if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier)) {
send();
}
if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) {
close(false);
}
}
private void send() {
final int bytesRead;
try {
bytesRead = inputStream.read(buffer);
} catch (final IOException e) {
close(true);
e.printStackTrace();
return;
}
if (bytesRead == -1) {
sendFinish();
} else {
sendData(bytesRead);
}
}
private void sendData(final int bytesRead) {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toUpload.getId());
FriendlyByteBuf.writeInt(bytesRead);
FriendlyByteBuf.writeBytes(buffer, 0, bytesRead);
getPartner().sendPacket(PacketType.SEND_LITEMATIC.identifier, FriendlyByteBuf);
}
private void sendFinish() {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toUpload.getId());
getPartner().sendPacket(PacketType.FINISHED_LITEMATIC.identifier, FriendlyByteBuf);
succeed();
}
@Override
public void init() {
send();
}
@Override
protected void onClose() {
try {
inputStream.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
@Override
protected void sendCancelPacket() {
final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer());
FriendlyByteBuf.writeUUID(toUpload.getId());
getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf);
}
}

View File

@@ -0,0 +1,66 @@
package org.leavesmc.leaves.protocol.syncmatica.exchange;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager;
import org.leavesmc.leaves.protocol.syncmatica.FeatureSet;
import org.leavesmc.leaves.protocol.syncmatica.PacketType;
import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
import java.util.Collection;
public class VersionHandshakeServer extends FeatureExchange {
public VersionHandshakeServer(final ExchangeTarget partner) {
super(partner);
}
@Override
public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
return id.equals(PacketType.REGISTER_VERSION.identifier)
|| super.checkPacket(id, packetBuf);
}
@Override
public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) {
if (id.equals(PacketType.REGISTER_VERSION.identifier)) {
String partnerVersion = packetBuf.readUtf();
if (partnerVersion.equals("0.0.1")) {
close(false);
return;
}
final FeatureSet fs = FeatureSet.fromVersionString(partnerVersion);
if (fs == null) {
requestFeatureSet();
} else {
getPartner().setFeatureSet(fs);
onFeatureSetReceive();
}
} else {
super.handle(id, packetBuf);
}
}
@Override
public void onFeatureSetReceive() {
final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
final Collection<ServerPlacement> l = SyncmaticaProtocol.getSyncmaticManager().getAll();
newBuf.writeInt(l.size());
for (final ServerPlacement p : l) {
CommunicationManager.putMetaData(p, newBuf, getPartner());
}
getPartner().sendPacket(PacketType.CONFIRM_USER.identifier, newBuf);
succeed();
}
@Override
public void init() {
final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
newBuf.writeUtf(SyncmaticaProtocol.PROTOCOL_VERSION);
getPartner().sendPacket(PacketType.REGISTER_VERSION.identifier, newBuf);
}
}

Some files were not shown because too many files have changed in this diff Show More