Add support for more mod protocols (#73)

* Add Syncmatica protocols

* Add AppleSkin protocols

* Add BBOR Protocol

* Add Jade Protocol

* Add Xaero Map Protocol

* Add Support REI protocol

* Fixed an issue where the configuration file was not loading correctly.

* move config file directory

* add license & refactor code

* update leaves fix

https://github.com/LeavesMC/Leaves/issues/747#event-19986621829

* Update Luminol

* fix up jade protocol

* fix up rei

---------

Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun>
Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
This commit is contained in:
xiaoxijun
2025-10-20 00:06:40 +08:00
committed by GitHub
parent 4cc49c6d99
commit 4ca94280ae
118 changed files with 9666 additions and 9 deletions
@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 30 Aug 2025 17:16:32 +0800
Subject: [PATCH] Leaves: Leaves Fakeplayer
Subject: [PATCH] Leaves: Fakeplayer
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Mon, 3 Feb 2025 16:51:01 +0800
Subject: [PATCH] Leaves: Syncmatica Protocol
This patch is Powered by Syncmatica(https://github.com/End-Tech/syncmatica)
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index e17f9629369f8653b484b4cc71de4874b5faa4d6..9be4bc9e78c22c12ba594b16dac6901023f0fd47 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -330,9 +330,12 @@ public class ServerGamePacketListenerImpl
this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile);
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.exchangeTarget = new org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget(this); // Leaves - Syncmatica Protocol
this.playerGameConnection = new io.papermc.paper.connection.PaperPlayerGameConnection(this); // Paper
}
+ public final org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // Leaves - Syncmatica Protocol
+
// Paper start - configuration phase API
@Override
public io.papermc.paper.connection.PlayerCommonConnection getApiConnection() {
@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Mon, 3 Feb 2025 13:03:42 +0800
Subject: [PATCH] Leaves: BBOR Protocol
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 29788fedd54b8411c9c8872b4d83b037874fb5f0..24af6821694768058dc0146220877ab70066b5f9 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -754,6 +754,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
public void setLoaded(boolean loaded) {
this.loaded = loaded;
+ // Leaves start - bbor
+ if (loaded) {
+ org.leavesmc.leaves.protocol.BBORProtocol.onChunkLoaded(this);
+ }
+ // Leaves end - bbor
}
public Level getLevel() {
@@ -0,0 +1,114 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Mon, 3 Feb 2025 13:33:19 +0800
Subject: [PATCH] Leaves: Jade Protocol
This patch is Powered by Jade(https://github.com/Snownee/Jade)
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
index c1798db2972c8f2a343cf6e16fd9354ff212d906..a8d617b16ab2b2c0cdb289a0aa05fa171940cd7e 100644
--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java
+++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
@@ -63,7 +63,7 @@ public class Armadillo extends Animal {
public final AnimationState rollOutAnimationState = new AnimationState();
public final AnimationState rollUpAnimationState = new AnimationState();
public final AnimationState peekAnimationState = new AnimationState();
- private int scuteTime;
+ public int scuteTime; // Leaves - private -> public
private boolean peekReceivedClient = false;
public Armadillo(EntityType<? extends Animal> entityType, Level level) {
diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java
index 17f58246849ed407821a987b200cc765eb7943f9..ac27df3ba0ce9bbdf2f32ea87171fbb9407008d6 100644
--- a/net/minecraft/world/entity/animal/frog/Tadpole.java
+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
@@ -254,7 +254,7 @@ public class Tadpole extends AbstractFish {
}
}
- private int getTicksLeftUntilAdult() {
+ public int getTicksLeftUntilAdult() { // Leaves - private -> public
return Math.max(0, ticksToBeFrog - this.age);
}
diff --git a/net/minecraft/world/level/storage/loot/LootPool.java b/net/minecraft/world/level/storage/loot/LootPool.java
index 6901e629d941e22e64d83eed4e8cfee3165a96a1..fdc26c8d8c82c20534c57af2a0281b99998cc9f6 100644
--- a/net/minecraft/world/level/storage/loot/LootPool.java
+++ b/net/minecraft/world/level/storage/loot/LootPool.java
@@ -37,7 +37,7 @@ public class LootPool {
)
.apply(instance, LootPool::new)
);
- private final List<LootPoolEntryContainer> entries;
+ public final List<LootPoolEntryContainer> entries; // Leaves - private -> public
private final List<LootItemCondition> conditions;
private final Predicate<LootContext> compositeCondition;
private final List<LootItemFunction> functions;
diff --git a/net/minecraft/world/level/storage/loot/LootTable.java b/net/minecraft/world/level/storage/loot/LootTable.java
index 8612cdf7161f8ddff60a6478cc901318b8f958ba..07a962d647baa99b0e1bf3898a07cc914e91397e 100644
--- a/net/minecraft/world/level/storage/loot/LootTable.java
+++ b/net/minecraft/world/level/storage/loot/LootTable.java
@@ -50,7 +50,7 @@ public class LootTable {
public static final LootTable EMPTY = new LootTable(LootContextParamSets.EMPTY, Optional.empty(), List.of(), List.of());
private final ContextKeySet paramSet;
private final Optional<ResourceLocation> randomSequence;
- private final List<LootPool> pools;
+ public final List<LootPool> pools; // Leaves - private -> public
private final List<LootItemFunction> functions;
private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
public org.bukkit.craftbukkit.CraftLootTable craftLootTable; // CraftBukkit
diff --git a/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java b/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java
index eeaa49e9f70a18b5d39493aeff73f31b05ac2faa..8cd0403d7873c4c37caef75935b06b056c3d951d 100644
--- a/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java
+++ b/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java
@@ -16,7 +16,7 @@ public abstract class CompositeEntryBase extends LootPoolEntryContainer {
return "Empty children list";
}
};
- protected final List<LootPoolEntryContainer> children;
+ public final List<LootPoolEntryContainer> children; // Leaves - private -> public
private final ComposableEntryContainer composedChildren;
protected CompositeEntryBase(List<LootPoolEntryContainer> children, List<LootItemCondition> conditions) {
diff --git a/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java
index 65e27bce9e59ef97bc8b914d646fba924d0f0877..a49bdcdf37b351436e0ba6d7865f10827c4e6ab4 100644
--- a/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java
+++ b/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java
@@ -14,7 +14,7 @@ import net.minecraft.world.level.storage.loot.predicates.ConditionUserBuilder;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
public abstract class LootPoolEntryContainer implements ComposableEntryContainer {
- protected final List<LootItemCondition> conditions;
+ public final List<LootItemCondition> conditions; // Leaves - private -> public
private final Predicate<LootContext> compositeCondition;
protected LootPoolEntryContainer(List<LootItemCondition> conditions) {
diff --git a/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java b/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java
index 141026601cd9a4561426b85fd1f8e7dc0544fbd7..a5d7ebb93c147bf0f806ac3c9b2dc4b878573944 100644
--- a/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java
+++ b/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java
@@ -29,7 +29,7 @@ public class NestedLootTable extends LootPoolSingletonContainer {
return "->{inline}";
}
};
- private final Either<ResourceKey<LootTable>, LootTable> contents;
+ public final Either<ResourceKey<LootTable>, LootTable> contents; // Leaves - private -> public
private NestedLootTable(
Either<ResourceKey<LootTable>, LootTable> contents, int weight, int quality, List<LootItemCondition> conditions, List<LootItemFunction> functions
diff --git a/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java b/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java
index bae72197acc929c7ed3e964f156115d728eb2176..8f3094f42f3366a1313d70c0b27fbe5632b2082a 100644
--- a/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java
+++ b/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java
@@ -12,7 +12,7 @@ import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.ValidationContext;
public abstract class CompositeLootItemCondition implements LootItemCondition {
- protected final List<LootItemCondition> terms;
+ public final List<LootItemCondition> terms; // Leaves - private -> public
private final Predicate<LootContext> composedPredicate;
protected CompositeLootItemCondition(List<LootItemCondition> terms, Predicate<LootContext> composedPredicate) {
@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Fri, 7 Feb 2025 14:23:43 +0800
Subject: [PATCH] Leaves: Xaero Map Protocol
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 908ec29a6e285122d1a8a43dc547b74af2697442..1bf1731e43278b71423fee5d92401c48d6dabaca 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1247,6 +1247,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); // Leaves - xaero map protocol
if (level.isRaining()) {
// CraftBukkit start - handle player weather
// player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
@@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Thu, 27 Mar 2025 13:04:35 +0800
Subject: [PATCH] Leaves: Support REI protocol
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
index 9bc0a9c3577d63a0ad5489bfd4c07d5006245c5f..bb2e891539b7eb49dc7925630695149aa531f672 100644
--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
@@ -83,6 +83,12 @@ public class SmithingTransformRecipe implements SmithingRecipe {
);
}
+ // Leaves start - REI
+ public SlotDisplay getResult() {
+ return this.result.display();
+ }
+ // Leaves end - REI
+
// CraftBukkit start
@Override
public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
diff --git a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
index c324896afc2ee28ebb4d426ce4a469ee847ce24d..d5b26ff7be916e07e1162536cb2ddf5674c82f46 100644
--- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
@@ -85,6 +85,12 @@ public class SmithingTrimRecipe implements SmithingRecipe {
return Optional.of(this.addition);
}
+ // Leaves start
+ public Holder<TrimPattern> pattern() {
+ return pattern;
+ }
+ // Leaves end
+
@Override
public RecipeSerializer<SmithingTrimRecipe> getSerializer() {
return RecipeSerializer.SMITHING_TRIM;
@@ -161,7 +161,7 @@ index 852e1ffef6a022caad7c8eff34091e50112a2290..8eb5d014d9ed688ffebaffb4ce0bb408
if (entity instanceof EnderDragonPart complexPart) {
if (complexPart.parentMob instanceof EnderDragon) {
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index e040f9a7c9451fbe547dda56952410047a99ddc0..05753de195bd7f4ac2952183662a8cc9ec8f4ba0 100644
index 317837a2a9b3b511b5cac4df4db248d4652816dc..a8253d1a3129c8258e9ead7c430e1bcf82f735d7 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -897,7 +897,11 @@ public class CraftEventFactory {
@@ -0,0 +1,16 @@
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "appleskin", directory = {"protocol"})
public class AppleSkinProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable AppleSkin protocol support""")
public static boolean enabled = false;
@ConfigInfo(name = "sync-tick-interval", comments = """
Set AppleSkin Synchronization Frequency (Unit: Game Ticks)""")
public static int syncTickInterval = 20;
}
@@ -0,0 +1,13 @@
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "bbor", directory = {"protocol"})
public class BBORProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable BBOR protocol support""")
public static boolean enabled = false;
}
@@ -0,0 +1,13 @@
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "jade", directory = {"protocol"})
public class JadeProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable Jade protocol support""")
public static boolean enabled = false;
}
@@ -0,0 +1,13 @@
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "rei", directory = {"protocol"})
public class REIServerProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable Roughly Enough Items protocol support""")
public static boolean enabled = false;
}
@@ -1,4 +1,4 @@
package fun.bm.lophine.config.modules.function;
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.CommandSuggestions;
@@ -9,54 +9,64 @@ import me.earthme.luminol.enums.EnumConfigCategory;
import java.util.List;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "servux-protocol")
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "servux", directory = {"protocol"})
public class ServuxProtocolConfig implements IConfigModule {
@TransformedConfig(name = "entity-protocol", directory = {"function", "servux-protocol", "data"})
@TransformedConfig(name = "entity-protocol", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "entity-protocol", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "entity-protocol", directory = {"data"})
public static boolean entityProtocol = false;
@TransformedConfig(name = "hud-logger-protocol", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "hud-logger-protocol", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "hud-logger-protocol", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "hud-logger-protocol")
public static boolean hudLoggerProtocol = false;
@TransformedConfig(name = "hud-metadata-protocol", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "hud-metadata-protocol", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "hud-metadata-protocol", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "hud-metadata-protocol")
public static boolean hudMetadataProtocol = false;
@TransformedConfig(name = "hud-metadata-share-seed", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "hud-metadata-share-seed", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "hud-metadata-share-seed", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "hud-metadata-share-seed")
public static boolean hudMetadataShareSeed = false;
@TransformedConfig(name = "structure-protocol", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "structure-protocol", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "structure-protocol", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "structure-protocol")
public static boolean structureProtocol = false;
@TransformedConfig(name = "hud-enabled-loggers", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "hud-enabled-loggers", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "hud-enabled-loggers", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "hud-enabled-loggers")
public static List<String> hudEnabledLoggers = List.of("tps", "mob_caps");
@TransformedConfig(name = "hud-update-interval", directory = {"function", "servux-protocol"})
@TransformedConfig(name = "hud-update-interval", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "hud-update-interval", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "hud-update-interval")
public static int hudUpdateInterval = 1;
@TransformedConfig(name = "litematics-enabled", directory = {"function", "servux-protocol", "litematics"})
@TransformedConfig(name = "litematics-enabled", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "litematics-enabled", directory = {"misc", "survux-protocol"})
@ConfigInfo(name = "litematics-enabled", directory = {"litematics"})
public static boolean litematicsEnabled = false;
@TransformedConfig(name = "litematics-max-nbt-size", directory = {"function", "servux-protocol", "litematics"})
@TransformedConfig(name = "litematics-max-nbt-size", directory = {"function", "survux-protocol"})
@TransformedConfig(name = "litematics-max-nbt-size", directory = {"misc", "survux-protocol"})
@CommandSuggestions(suggest = {"-1", "2097152"})
@ConfigInfo(name = "litematics-max-nbt-size", directory = {"litematics"})
public static int litematicsMaxNbtSize = 2097152;
@TransformedConfig(name = "litematics-print-max-delay-ticks", directory = {"function", "servux-protocol", "litematics"})
@TransformedConfig(name = "litematics-print-max-delay-ticks", directory = {"function", "survux-protocol"})
@CommandSuggestions(suggest = {"-1", "1200"})
@ConfigInfo(name = "litematics-print-max-delay-ticks", directory = {"litematics"}, comments = "The max delay ticks for printing litematics, -1 to disable")
@@ -0,0 +1,25 @@
package fun.bm.lophine.config.modules.function.protocol;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "syncmatica", directory = {"protocol"})
public class SyncmaticaProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable Syncmatica protocol support""")
public static boolean enabled = false;
@ConfigInfo(name = "useQuota", comments = """
Is there a limit on the size of projection files?""")
public static boolean useQuota = false;
@ConfigInfo(name = "quota-Limit", comments = """
Maximum Projection File Size (in bytes)""")
public static int quotaLimit = 40000000;
public void onLoaded(CommentedFileConfig configInstance) {
SyncmaticaProtocol.init(enabled);
}
}
@@ -0,0 +1,17 @@
package fun.bm.lophine.config.modules.function.protocol;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import java.util.Random;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "xaero-map", directory = {"protocol"})
public class XaeroMapProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Enable Xaero World Map Protocol Support""")
public static boolean enabled = false;
@ConfigInfo(name = "xaeroMapServerID")
public static int xaeroMapServerID = new Random().nextInt();
}
@@ -0,0 +1,141 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol;
import fun.bm.lophine.config.modules.function.protocol.AppleSkinProtocolConfig;
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.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.Context;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.*;
@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<UUID, 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.getUUID());
resetPlayerData(player);
}
@ProtocolHandler.MinecraftRegister(onlyNamespace = true)
public static void onPlayerSubscribed(@NotNull Context context, ResourceLocation id) {
subscribedChannels.computeIfAbsent(context.profile().getId(), k -> new HashSet<>()).add(id.getPath());
}
@ProtocolHandler.Ticker
public static void tick() {
for (Map.Entry<UUID, Set<String>> entry : subscribedChannels.entrySet()) {
ServerPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(entry.getKey());
if (player == null) {
continue;
}
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 AppleSkinProtocolConfig.syncTickInterval;
}
@Override
public boolean isActive() {
return AppleSkinProtocolConfig.enabled;
}
}
@@ -0,0 +1,244 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol;
import fun.bm.lophine.config.modules.function.protocol.BBORProtocolConfig;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
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;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@LeavesProtocol.Register(namespace = "bbor")
public class BBORProtocol implements LeavesProtocol {
public static final String PROTOCOL_ID = "bbor";
// send
private static final ResourceLocation INITIALIZE_CLIENT = id("initialize");
private static final ResourceLocation ADD_BOUNDING_BOX = id("add_bounding_box_v2");
private static final ResourceLocation STRUCTURE_LIST_SYNC = id("structure_list_sync_v1");
// call
private static final Map<Integer, ServerPlayer> players = new ConcurrentHashMap<>();
private static final Map<Integer, Set<BBoundingBox>> playerBoundingBoxesCache = new HashMap<>();
private static final Map<ResourceLocation, Map<BBoundingBox, Set<BBoundingBox>>> dimensionCache = new ConcurrentHashMap<>();
private static boolean initialized = false;
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.Ticker
public static void tick() {
for (var playerEntry : players.entrySet()) {
sendBoundingToPlayer(playerEntry.getKey(), playerEntry.getValue());
}
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
if (BBORProtocolConfig.enabled) {
initAllPlayer();
} else {
loggedOutAllPlayer();
}
}
@ProtocolHandler.PlayerJoin
public static void onPlayerLoggedIn(@NotNull ServerPlayer player) {
ServerLevel overworld = MinecraftServer.getServer().overworld();
ProtocolUtils.sendBytebufPacket(player, INITIALIZE_CLIENT, buf -> {
buf.writeLong(overworld.getSeed());
buf.writeInt(overworld.levelData.getSpawnPos().getX());
buf.writeInt(overworld.levelData.getSpawnPos().getZ());
});
sendStructureList(player);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
players.remove(player.getId());
playerBoundingBoxesCache.remove(player.getId());
}
@ProtocolHandler.BytebufReceiver(key = "subscribe")
public static void onPlayerSubscribed(@NotNull ServerPlayer player, FriendlyByteBuf buf) {
players.put(player.getId(), player);
sendBoundingToPlayer(player.getId(), player);
}
@ProtocolHandler.ReloadDataPack
public static void onDataPackReload() {
players.values().forEach(BBORProtocol::sendStructureList);
}
public static void onChunkLoaded(@NotNull LevelChunk chunk) {
Map<String, StructureStart> structures = new HashMap<>();
final Registry<Structure> structureFeatureRegistry = chunk.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE);
for (var es : chunk.getAllStarts().entrySet()) {
final var optional = structureFeatureRegistry.getResourceKey(es.getKey());
optional.ifPresent(key -> structures.put(key.location().toString(), es.getValue()));
}
if (!structures.isEmpty()) {
onStructuresLoaded(chunk.getLevel().dimension().location(), structures);
}
}
public static void onStructuresLoaded(@NotNull ResourceLocation dimensionID, @NotNull Map<String, StructureStart> structures) {
Map<BBoundingBox, Set<BBoundingBox>> cache = getOrCreateCache(dimensionID);
for (var entry : structures.entrySet()) {
StructureStart structureStart = entry.getValue();
if (structureStart == null) {
return;
}
String type = "structure:" + entry.getKey();
BoundingBox bb = structureStart.getBoundingBox();
BBoundingBox boundingBox = buildStructure(bb, type);
if (cache.containsKey(boundingBox)) {
return;
}
Set<BBoundingBox> structureBoundingBoxes = new HashSet<>();
for (StructurePiece structureComponent : structureStart.getPieces()) {
structureBoundingBoxes.add(buildStructure(structureComponent.getBoundingBox(), type));
}
cache.put(boundingBox, structureBoundingBoxes);
}
}
private static @NotNull BBoundingBox buildStructure(@NotNull BoundingBox bb, String type) {
BlockPos min = new BlockPos(bb.minX(), bb.minY(), bb.minZ());
BlockPos max = new BlockPos(bb.maxX(), bb.maxY(), bb.maxZ());
return new BBoundingBox(type, min, max);
}
private static void sendStructureList(@NotNull ServerPlayer player) {
final Registry<Structure> structureRegistry = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.STRUCTURE);
final Set<String> structureIds = structureRegistry.entrySet().stream()
.map(e -> e.getKey().location().toString()).collect(Collectors.toSet());
ProtocolUtils.sendBytebufPacket(player, STRUCTURE_LIST_SYNC, buf -> {
buf.writeVarInt(structureIds.size());
structureIds.forEach(buf::writeUtf);
});
}
private static void sendBoundingToPlayer(int id, ServerPlayer player) {
for (var entry : dimensionCache.entrySet()) {
if (entry.getValue() == null) {
return;
}
Set<BBoundingBox> playerBoundingBoxes = playerBoundingBoxesCache.computeIfAbsent(id, k -> new HashSet<>());
Map<BBoundingBox, Set<BBoundingBox>> boundingBoxMap = entry.getValue();
for (BBoundingBox key : boundingBoxMap.keySet()) {
if (playerBoundingBoxes.contains(key)) {
continue;
}
Set<BBoundingBox> boundingBoxes = boundingBoxMap.get(key);
ProtocolUtils.sendBytebufPacket(player, ADD_BOUNDING_BOX, buf -> {
buf.writeResourceLocation(entry.getKey());
key.serialize(buf);
if (boundingBoxes != null && boundingBoxes.size() > 1) {
for (BBoundingBox box : boundingBoxes) {
box.serialize(buf);
}
}
});
playerBoundingBoxes.add(key);
}
}
}
public static void initAllPlayer() {
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
onPlayerLoggedIn(player);
}
initialized = true;
}
public static void loggedOutAllPlayer() {
players.clear();
playerBoundingBoxesCache.clear();
for (var cache : dimensionCache.values()) {
cache.clear();
}
dimensionCache.clear();
}
private static Map<BBoundingBox, Set<BBoundingBox>> getOrCreateCache(ResourceLocation dimensionId) {
return dimensionCache.computeIfAbsent(dimensionId, dt -> new ConcurrentHashMap<>());
}
@Override
public boolean isActive() {
boolean active = BBORProtocolConfig.enabled;
if (!active && initialized) {
initialized = false;
loggedOutAllPlayer();
}
return active;
}
private record BBoundingBox(String type, BlockPos min, BlockPos max) {
private static int combineHashCodes(int @NotNull ... hashCodes) {
final int prime = 31;
int result = 0;
for (int hashCode : hashCodes) {
result = prime * result + hashCode;
}
return result;
}
public void serialize(@NotNull FriendlyByteBuf buf) {
buf.writeChar('S');
buf.writeInt(type.hashCode());
buf.writeVarInt(min.getX()).writeVarInt(min.getY()).writeVarInt(min.getZ());
buf.writeVarInt(max.getX()).writeVarInt(max.getY()).writeVarInt(max.getZ());
}
@Override
public int hashCode() {
return combineHashCodes(min.hashCode(), max.hashCode());
}
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol;
import fun.bm.lophine.config.modules.function.protocol.XaeroMapProtocolConfig;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
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 (XaeroMapProtocolConfig.enabled) {
ProtocolUtils.sendBytebufPacket(player, MINIMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(XaeroMapProtocolConfig.xaeroMapServerID);
});
ProtocolUtils.sendBytebufPacket(player, WORLDMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(XaeroMapProtocolConfig.xaeroMapServerID);
});
}
}
@Override
public boolean isActive() {
return XaeroMapProtocolConfig.enabled;
}
}
@@ -0,0 +1,266 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.protocol.JadeProtocolConfig;
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.*;
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 org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.payload.*;
import org.leavesmc.leaves.protocol.jade.provider.*;
import org.leavesmc.leaves.protocol.jade.provider.block.*;
import org.leavesmc.leaves.protocol.jade.provider.entity.*;
import org.leavesmc.leaves.protocol.jade.util.*;
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
import org.slf4j.Logger;
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 {
private static final Logger LOGGER = LogUtils.getLogger();
public static final String PROTOCOL_ID = "jade";
public static final String PROTOCOL_VERSION = "8";
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<>();
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) {
player.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
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) {
LOGGER.warn("Error while saving data for entity " + entity);
}
}
tag.putInt("EntityId", entity.getId());
ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag));
}, null, 1L);
}
@ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class)
public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) {
player.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
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) {
LOGGER.warn("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));
}, null, 1L);
}
@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();
LOGGER.warn("Failed to collect shearable blocks");
}
}
@Override
public boolean isActive() {
return JadeProtocolConfig.enabled;
}
}
@@ -0,0 +1,39 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.Nullable;
public interface Accessor<T extends HitResult> {
ServerLevel getLevel();
Player getPlayer();
<D> Tag encodeAsNbt(StreamEncoder<RegistryFriendlyByteBuf, D> codec, D value);
T getHitResult();
@Nullable
Object getTarget();
}
@@ -0,0 +1,77 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
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 ServerLevel level;
private final Player player;
private final Supplier<T> hit;
protected boolean verify;
private RegistryFriendlyByteBuf buffer;
public AccessorImpl(ServerLevel level, Player player, Supplier<T> hit) {
this.level = level;
this.player = player;
this.hit = hit;
}
@Override
public ServerLevel 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();
}
}
@@ -0,0 +1,61 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
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(ServerLevel 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();
}
}
@@ -0,0 +1,160 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
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 ServerLevel level;
private Player player;
private BlockHitResult hit;
private BlockState blockState = Blocks.AIR.defaultBlockState();
private Supplier<BlockEntity> blockEntity;
@Override
public Builder level(ServerLevel 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();
}
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
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(ServerLevel 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();
}
}
@@ -0,0 +1,129 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
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 ServerLevel level;
private Player player;
private Supplier<EntityHitResult> hit;
private Supplier<Entity> entity;
@Override
public Builder level(ServerLevel 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();
}
}
}
@@ -0,0 +1,36 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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
);
}
@@ -0,0 +1,37 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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
);
}
@@ -0,0 +1,53 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
@@ -0,0 +1,54 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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
);
}
@@ -0,0 +1,29 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade.provider;
import net.minecraft.resources.ResourceLocation;
public interface IJadeProvider {
ResourceLocation getUid();
default int getDefaultPriority() {
return 0;
}
}
@@ -0,0 +1,25 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
@@ -0,0 +1,27 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
@@ -0,0 +1,165 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade.provider;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mojang.logging.LogUtils;
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.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.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 org.slf4j.Logger;
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");
private static final Logger LOGGER = LogUtils.getLogger();
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(),
true // Bypass lock check
);
}
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) {
LOGGER.warn("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;
}
}
@@ -0,0 +1,104 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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();
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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();
}
@@ -0,0 +1,51 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,77 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -0,0 +1,72 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,61 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,57 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,68 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -0,0 +1,54 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,50 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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 = 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;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,65 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,61 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,52 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,54 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.jade.util;
import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
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.protocol.jade.accessor.Accessor;
import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class CommonUtil {
private static final Logger LOGGER = LogUtils.getLogger();
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) {
LOGGER.warn(e.toString());
continue;
}
if (groups != null) {
return Map.entry(provider.getUid(), groups);
}
}
return null;
}
}
@@ -0,0 +1,147 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.*;
import com.mojang.logging.LogUtils;
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 org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
public class HierarchyLookup<T extends IJadeProvider> implements IHierarchyLookup<T> {
private static final Logger LOGGER = LogUtils.getLogger();
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) {
LOGGER.warn("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();
}
}
}
@@ -0,0 +1,84 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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.*;
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;
}
}
@@ -0,0 +1,138 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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().getServer().getTickCount();
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(container, 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;
}
}
}
@@ -0,0 +1,119 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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 float progress;
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);
protected abstract int getSlotCount(T container);
public void reset() {
currentIndex = fromIndex;
finished = false;
}
public void afterPopulate(T container, int count) {
currentIndex += count;
if (count == 0 || currentIndex >= 10000) {
finished = true;
}
progress = (float) (currentIndex - fromIndex) / (getSlotCount(container) - fromIndex);
}
public float getCollectingProgress() {
return Float.NaN;
}
public static abstract class SlottedItemIterator<T> extends ItemIterator<T> {
public SlottedItemIterator(Function<Object, @Nullable T> containerFinder, int fromIndex) {
super(containerFinder, fromIndex);
}
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;
}
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);
}
}
}
@@ -0,0 +1,77 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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()));
}
}
};
}
@@ -0,0 +1,126 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,134 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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 com.mojang.logging.LogUtils;
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.protocol.jade.provider.IJadeProvider;
import org.slf4j.Logger;
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> {
private static final Logger LOGGER = LogUtils.getLogger();
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) {
LOGGER.warn(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();
}
}
}
@@ -0,0 +1,57 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -0,0 +1,75 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,124 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,163 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei;
import com.mojang.logging.LogUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiConsumer;
public class PacketTransformer {
private static final Logger LOGGER = LogUtils.getLogger();
private static final byte START = 0x0;
private static final byte PART = 0x1;
private static final byte END = 0x2;
private static final byte ONLY = 0x3;
private final Map<UUID, PartData> cache = Collections.synchronizedMap(new HashMap<>());
public static DiscardedPayload wrapRei(ResourceLocation location, FriendlyByteBuf buf) {
FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
newBuf.writeByteArray(ByteBufUtil.getBytes(buf));
return new DiscardedPayload(location, ByteBufUtil.getBytes(newBuf));
}
public void inbound(ResourceLocation id, RegistryFriendlyByteBuf buf, ServerPlayer player, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
UUID key = player.getUUID();
PartData data;
buf.readVarInt();
switch (buf.readByte()) {
case START -> {
int partsNum = buf.readInt();
data = new PartData(id, partsNum);
if (cache.put(key, data) != null) {
LOGGER.warn("Received invalid START packet for SplitPacketTransformer with packet id " + id);
}
buf.retain();
data.parts.add(buf);
}
case PART -> {
if ((data = cache.get(key)) == null) {
LOGGER.warn("Received invalid PART packet for SplitPacketTransformer with packet id " + id);
buf.release();
} else if (!data.id.equals(id)) {
LOGGER.warn("Received invalid PART packet for SplitPacketTransformer with packet id " + id + ", id in cache is {}" + data.id);
buf.release();
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
cache.remove(key);
} else {
buf.retain();
data.parts.add(buf);
}
}
case END -> {
if ((data = cache.get(key)) == null) {
LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id {}" + id);
buf.release();
} else if (!data.id.equals(id)) {
LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + ", id in cache is {}" + data.id);
buf.release();
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
cache.remove(key);
} else {
buf.retain();
data.parts.add(buf);
}
if (data == null) {
return;
}
if (data.parts.size() != data.partsNum) {
LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " with size " + data.parts + ", parts expected is {}" + data.partsNum);
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
} else {
RegistryFriendlyByteBuf byteBuf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(data.parts.toArray(new ByteBuf[0])), buf.registryAccess());
consumer.accept(data.id, byteBuf);
byteBuf.release();
}
cache.remove(key);
}
case ONLY -> consumer.accept(id, buf);
default -> throw new IllegalStateException("Illegal split packet header!");
}
}
public void outbound(ResourceLocation id, RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
int maxSize = 1048576 - 1 - 20 - id.toString().getBytes(StandardCharsets.UTF_8).length;
if (buf.readableBytes() <= maxSize) {
ByteBuf stateBuf = Unpooled.buffer(1);
stateBuf.writeByte(ONLY);
RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(stateBuf, buf), buf.registryAccess());
consumer.accept(id, packetBuffer);
} else {
int partSize = maxSize - 4;
int parts = (int) Math.ceil(buf.readableBytes() / (float) partSize);
for (int i = 0; i < parts; i++) {
RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), buf.registryAccess());
if (i == 0) {
packetBuffer.writeByte(START);
packetBuffer.writeInt(parts);
} else if (i == parts - 1) {
packetBuffer.writeByte(END);
} else {
packetBuffer.writeByte(PART);
}
int next = Math.min(buf.readableBytes(), partSize);
packetBuffer.writeBytes(buf.retainedSlice(buf.readerIndex(), next));
buf.skipBytes(next);
consumer.accept(id, packetBuffer);
}
buf.release();
}
}
private static class PartData {
private final ResourceLocation id;
private final int partsNum;
private final List<RegistryFriendlyByteBuf> parts;
public PartData(ResourceLocation id, int partsNum) {
this.id = id;
this.partsNum = partsNum;
this.parts = new ArrayList<>();
}
}
}
@@ -0,0 +1,432 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.protocol.REIServerProtocolConfig;
import io.netty.buffer.Unpooled;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
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.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
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.rei.display.*;
import org.leavesmc.leaves.protocol.rei.payload.DisplaySyncPayload;
import org.leavesmc.leaves.protocol.rei.transfer.InputSlotCrafter;
import org.leavesmc.leaves.protocol.rei.transfer.NewInputSlotCrafter;
import org.leavesmc.leaves.protocol.rei.transfer.slot.PlayerInventorySlotAccessor;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import org.leavesmc.leaves.protocol.rei.transfer.slot.VanillaSlotAccessor;
import org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@LeavesProtocol.Register(namespace = REIServerProtocol.PROTOCOL_ID)
public class REIServerProtocol implements LeavesProtocol {
private static final Logger LOGGER = LogUtils.getLogger();
public static final String PROTOCOL_ID = "roughlyenoughitems";
public static final String CHEAT_PERMISSION = "leaves.protocol.rei.cheat";
public static final ResourceLocation DELETE_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "delete_item");
public static final ResourceLocation CREATE_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item");
public static final ResourceLocation CREATE_ITEMS_HOTBAR_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_hotbar");
public static final ResourceLocation CREATE_ITEMS_GRAB_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_grab");
public static final ResourceLocation CREATE_ITEMS_MESSAGE_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "ci_msg");
public static final ResourceLocation MOVE_ITEMS_NEW_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "move_items_new");
public static final ResourceLocation NOT_ENOUGH_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "og_not_enough"); // this pack is under to-do at rei-client, so we don't handle it
public static final ResourceLocation SYNC_DISPLAYS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "sync_displays");
public static final Map<ResourceLocation, PacketTransformer> TRANSFORMERS = Util.make(() -> {
ImmutableMap.Builder<ResourceLocation, PacketTransformer> builder = ImmutableMap.builder();
builder.put(SYNC_DISPLAYS_PACKET, new PacketTransformer());
builder.put(DELETE_ITEMS_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_GRAB_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_HOTBAR_PACKET, new PacketTransformer());
builder.put(MOVE_ITEMS_NEW_PACKET, new PacketTransformer());
return builder.build();
});
private static final Set<ServerPlayer> enabledPlayers = new HashSet<>();
private static final Executor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
private static int minecraftRecipeVer = 0;
private static int nextReiRecipeVer = -1;
private static ImmutableList<CustomPacketPayload> cachedPayloads;
@ProtocolHandler.ReloadDataPack
public static void onRecipeReload() {
minecraftRecipeVer = MinecraftServer.getServer().getTickCount();
}
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
public static void onConfigModify(boolean enabled) {
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
if (enabled) {
if (pluginManager.getPermission(CHEAT_PERMISSION) == null) {
pluginManager.addPermission(new Permission(CHEAT_PERMISSION, PermissionDefault.OP));
}
} else {
pluginManager.removePermission(CHEAT_PERMISSION);
enabledPlayers.clear();
}
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
enabledPlayers.remove(player);
}
@ProtocolHandler.Ticker
public static void tick() {
if (minecraftRecipeVer != nextReiRecipeVer) {
nextReiRecipeVer = minecraftRecipeVer;
executor.execute(() -> reloadRecipe(nextReiRecipeVer));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static void reloadRecipe(int reiRecipeVer) {
ImmutableList.Builder<Display> builder = ImmutableList.builder();
MinecraftServer server = MinecraftServer.getServer();
RecipeMap recipeMap = server.getRecipeManager().recipes;
recipeMap.byType(RecipeType.CRAFTING).forEach(holder -> {
switch (holder.value()) {
case ShapedRecipe ignored -> builder.add(new ShapedDisplay((RecipeHolder) holder));
case ShapelessRecipe ignored -> builder.add(new ShapelessDisplay((RecipeHolder) holder));
case TransmuteRecipe ignored -> builder.addAll(Display.ofTransmuteRecipe((RecipeHolder) holder));
case TippedArrowRecipe ignored -> builder.addAll(Display.ofTippedArrowRecipe((RecipeHolder) holder));
case FireworkRocketRecipe ignored ->
builder.addAll(Display.ofFireworkRocketRecipe((RecipeHolder) holder));
case MapCloningRecipe ignored -> builder.addAll(Display.ofMapCloningRecipe((RecipeHolder) holder));
// ignore ArmorDyeRecipe, BannerDuplicateRecipe, BookCloningRecipe, ShieldDecorationRecipe
default -> {
}
}
});
recipeMap.byType(RecipeType.STONECUTTING).forEach(holder -> builder.add(new StoneCuttingDisplay(holder)));
recipeMap.byType(RecipeType.SMELTING).forEach(holder -> builder.add(new SmeltingDisplay(holder)));
recipeMap.byType(RecipeType.BLASTING).forEach(holder -> builder.add(new BlastingDisplay(holder)));
recipeMap.byType(RecipeType.SMOKING).forEach(holder -> builder.add(new SmokingDisplay(holder)));
recipeMap.byType(RecipeType.CAMPFIRE_COOKING).forEach(holder -> builder.add(new CampfireDisplay(holder)));
recipeMap.byType(RecipeType.SMITHING).forEach(holder -> {
switch (holder.value()) {
case SmithingTrimRecipe ignored -> builder.addAll(Display.ofSmithingTrimRecipe((RecipeHolder) holder));
case SmithingTransformRecipe ignored -> builder.add(Display.ofTransforming((RecipeHolder) holder));
default -> {
}
}
});
DisplaySyncPayload displaySyncPayload = new DisplaySyncPayload(
DisplaySyncPayload.SyncType.SET,
builder.build(),
reiRecipeVer
);
RegistryFriendlyByteBuf s2cBuf = ProtocolUtils.decorate(Unpooled.buffer());
DisplaySyncPayload.STREAM_CODEC.encode(s2cBuf, displaySyncPayload);
ImmutableList.Builder<CustomPacketPayload> listBuilder = ImmutableList.builder();
outboundTransform(s2cBuf, (id, splitBuf) ->
listBuilder.add(PacketTransformer.wrapRei(id, splitBuf))
);
cachedPayloads = listBuilder.build();
Bukkit.getGlobalRegionScheduler().run(MinecraftInternalPlugin.INSTANCE, (task) -> {
for (ServerPlayer player : enabledPlayers) {
for (CustomPacketPayload payload : cachedPayloads) {
ProtocolUtils.sendPayloadPacket(player, payload);
}
}
});
}
@ProtocolHandler.MinecraftRegister(onlyNamespace = true, stage = ProtocolHandler.Stage.GAME)
public static void onPlayerSubscribed(@NotNull ServerPlayer player, ResourceLocation location) {
enabledPlayers.add(player);
String channel = location.getPath();
if (channel.equals("sync_displays")) {
if (cachedPayloads != null) {
cachedPayloads.forEach(payload -> ProtocolUtils.sendPayloadPacket(player, payload));
}
} else if (channel.equals("ci_msg")) {
// cheat rei-client into using "delete_item" packet
if (MinecraftServer.getServer().getProfilePermissions(player.getGameProfile()) < 1) {
player.getBukkitEntity().sendOpLevel((byte) 1);
}
}
}
@ProtocolHandler.BytebufReceiver(key = "delete_item")
public static void handleDeleteItem(ServerPlayer player, RegistryFriendlyByteBuf buf) {
if (!hasCheatPermission(player)) {
return;
}
inboundTransform(player, DELETE_ITEMS_PACKET, buf, (id, wholeBuf) -> {
AbstractContainerMenu menu = player.containerMenu;
if (!menu.getCarried().isEmpty()) {
menu.setCarried(ItemStack.EMPTY);
menu.broadcastChanges();
}
});
}
@ProtocolHandler.BytebufReceiver(key = "create_item")
public static void handleCreateItem(ServerPlayer player, RegistryFriendlyByteBuf buf) {
if (!hasCheatPermission(player)) {
return;
}
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack itemStack = tmpBuf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC);
if (player.getInventory().add(itemStack.copy())) {
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, itemStack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
} else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false);
}
};
inboundTransform(player, CREATE_ITEMS_PACKET, buf, consumer);
}
@ProtocolHandler.BytebufReceiver(key = "create_item_grab")
public static void handleCreateItemGrab(ServerPlayer player, RegistryFriendlyByteBuf buf) {
if (!hasCheatPermission(player)) {
return;
}
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack itemStack = tmpBuf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC);
ItemStack stack = itemStack.copy();
AbstractContainerMenu menu = player.containerMenu;
if (!menu.getCarried().isEmpty() && ItemStack.isSameItemSameComponents(menu.getCarried(), stack)) {
stack.setCount(Mth.clamp(stack.getCount() + menu.getCarried().getCount(), 1, stack.getMaxStackSize()));
} else if (!menu.getCarried().isEmpty()) {
return;
}
menu.setCarried(stack.copy());
menu.broadcastChanges();
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, stack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
};
inboundTransform(player, CREATE_ITEMS_GRAB_PACKET, buf, consumer);
}
@ProtocolHandler.BytebufReceiver(key = "create_item_hotbar")
public static void handleCreateItemHotbar(ServerPlayer player, RegistryFriendlyByteBuf buf) {
if (!hasCheatPermission(player)) {
return;
}
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack stack = tmpBuf.readLenientJsonWithCodec(ItemStack.OPTIONAL_CODEC);
int hotbarSlotId = tmpBuf.readVarInt();
if (hotbarSlotId >= 0 && hotbarSlotId < 9) {
AbstractContainerMenu menu = player.containerMenu;
player.getInventory().getNonEquipmentItems().set(hotbarSlotId, stack.copy());
menu.broadcastChanges();
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, stack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
} else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false);
}
};
inboundTransform(player, CREATE_ITEMS_HOTBAR_PACKET, buf, consumer);
}
@ProtocolHandler.BytebufReceiver(key = "move_items_new")
public static void handleMoveItem(ServerPlayer player, RegistryFriendlyByteBuf buf) {
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
AbstractContainerMenu container = player.containerMenu;
tmpBuf.readResourceLocation();
try {
boolean shift = tmpBuf.readBoolean();
try {
CompoundTag nbt = tmpBuf.readNbt();
if (nbt == null) {
throw new IllegalStateException("NBT data is null");
}
int version = nbt.getInt("Version").orElse(-1);
if (version != 1) {
throw new IllegalStateException("Server and client REI protocol version mismatch! Server: 1, Client: " + version);
}
List<List<ItemStack>> recipes = readInputs(player.registryAccess(), nbt.getListOrEmpty("Inputs"));
List<SlotAccessor> input = readSlots(container, player, nbt.getListOrEmpty("InputSlots"));
List<SlotAccessor> inventory = readSlots(container, player, nbt.getListOrEmpty("InventorySlots"));
NewInputSlotCrafter<AbstractContainerMenu> crafter = new NewInputSlotCrafter<>(container, input, inventory, recipes);
Bukkit.getGlobalRegionScheduler().run(MinecraftInternalPlugin.INSTANCE, (task) -> {
try {
crafter.fillInputSlots(player, shift);
} catch (InputSlotCrafter.NotEnoughMaterialsException ignored1) {
} catch (IllegalStateException e) {
player.sendSystemMessage(Component.translatable(e.getMessage()).withStyle(ChatFormatting.RED));
} catch (Exception e) {
player.sendSystemMessage(Component.translatable("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED));
LOGGER.warn("Failed to move items for player " + player.getScoreboardName(), e);
}
});
} catch (IllegalStateException e) {
player.sendSystemMessage(Component.translatable(e.getMessage()).withStyle(ChatFormatting.RED));
} catch (Exception e) {
player.sendSystemMessage(Component.translatable("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED));
LOGGER.warn("Failed to move items for player " + player.getScoreboardName(), e);
}
} catch (Exception e) {
LOGGER.warn("Failed to move items for player " + player.getScoreboardName(), e);
}
};
inboundTransform(player, MOVE_ITEMS_NEW_PACKET, buf, consumer);
}
private static void inboundTransform(ServerPlayer player, ResourceLocation id, RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
PacketTransformer transformer = TRANSFORMERS.get(id);
if (transformer != null) {
transformer.inbound(id, buf, player, consumer);
} else {
consumer.accept(id, buf);
}
}
private static void outboundTransform(RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
PacketTransformer transformer = TRANSFORMERS.get(SYNC_DISPLAYS_PACKET);
if (transformer != null) {
transformer.outbound(SYNC_DISPLAYS_PACKET, buf, consumer);
} else {
consumer.accept(SYNC_DISPLAYS_PACKET, buf);
}
}
private static boolean hasCheatPermission(ServerPlayer player) {
if (player.getBukkitEntity().hasPermission(CHEAT_PERMISSION)) {
return true;
}
player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false);
return false;
}
@Override
public boolean isActive() {
return REIServerProtocolConfig.enabled;
}
@Override
public int tickerInterval(String tickerID) {
return 200;
}
private static List<List<ItemStack>> readInputs(RegistryAccess registryAccess, ListTag tag) {
List<List<ItemStack>> items = new ArrayList<>();
for (Tag t : tag) {
CompoundTag compoundTag = (CompoundTag) t;
compoundTag.getInt("Index").orElseThrow();
ListTag ingredientList = compoundTag.getListOrEmpty("Ingredient");
List<ItemStack> slotItems = new ArrayList<>();
for (Tag ingredient : ingredientList) {
CompoundTag ingredientTag = (CompoundTag) ingredient;
ItemStack stack = ItemStack.OPTIONAL_CODEC.parse(
registryAccess.createSerializationContext(NbtOps.INSTANCE),
ingredientTag.get("value")
).getOrThrow();
slotItems.add(stack);
}
items.add(slotItems);
}
return items;
}
private static List<SlotAccessor> readSlots(AbstractContainerMenu menu, ServerPlayer player, ListTag tag) {
List<SlotAccessor> slots = new ArrayList<>();
for (Tag t : tag) {
CompoundTag compoundTag = (CompoundTag) t;
String id = compoundTag.getString("id").orElseThrow();
if (!id.startsWith(PROTOCOL_ID + ":")) {
throw new IllegalStateException("Invalid slot id: " + id + ", expected to start with '" + PROTOCOL_ID + ":'");
}
id = id.substring((PROTOCOL_ID + ":").length());
int slot = compoundTag.getInt("Slot").orElseThrow();
SlotAccessor accessor = switch (id) {
case "vanilla" -> new VanillaSlotAccessor(menu.slots.get(slot));
case "player" -> new PlayerInventorySlotAccessor(player, slot);
default -> throw new IllegalStateException("Unknown container id: " + id);
};
slots.add(accessor);
}
return slots;
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class BlastingDisplay extends CookingDisplay {
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/blasting");
public BlastingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
super(recipe);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.CampfireCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class CampfireDisplay extends CookingDisplay {
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/campfire");
public CampfireDisplay(RecipeHolder<CampfireCookingRecipe> recipe) {
super(recipe);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}
@@ -0,0 +1,84 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
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.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
public abstract class CookingDisplay extends Display {
private static final StreamCodec<RegistryFriendlyByteBuf, CookingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CookingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CookingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CookingDisplay::getOptionalLocation,
ByteBufCodecs.FLOAT,
CookingDisplay::getXp,
ByteBufCodecs.DOUBLE,
CookingDisplay::getCookTime,
CookingDisplay::of
);
protected float xp;
protected double cookTime;
private CookingDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation id, float xp, double cookTime) {
super(inputs, outputs, id);
this.xp = xp;
this.cookTime = cookTime;
}
public CookingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
this(
List.of(EntryIngredient.ofIngredient(recipe.value().input())),
List.of(EntryIngredient.of(recipe.value().assemble(new SingleRecipeInput(ItemStack.EMPTY), CraftRegistry.getMinecraftRegistry()))),
recipe.id().location(),
recipe.value().experience(),
recipe.value().cookingTime()
);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CookingDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id, float xp, double cookTime) {
throw new UnsupportedOperationException();
}
public float getXp() {
return xp;
}
public double getCookTime() {
return cookTime;
}
public StreamCodec<RegistryFriendlyByteBuf, CookingDisplay> streamCodec() {
return CODEC;
}
}
@@ -0,0 +1,38 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
public abstract class CraftingDisplay extends Display {
public CraftingDisplay(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation location) {
super(inputs, outputs, location);
}
public abstract int getWidth();
public abstract int getHeight();
}
@@ -0,0 +1,88 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
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.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.BitSet;
import java.util.List;
import java.util.Optional;
public class CustomDisplay extends CraftingDisplay {
private static final StreamCodec<RegistryFriendlyByteBuf, CustomDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CustomDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CustomDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CustomDisplay::getOptionalLocation,
CustomDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/custom");
private final int width;
private final int height;
/**
* see me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomDisplay#DefaultCustomDisplay
*/
public CustomDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation location) {
super(inputs, outputs, location);
BitSet row = new BitSet(3);
BitSet column = new BitSet(3);
for (int i = 0; i < 9; i++) {
if (i < inputs.size()) {
EntryIngredient stacks = inputs.get(i);
if (stacks.stream().anyMatch(stack -> !stack.isEmpty())) {
row.set((i - (i % 3)) / 3);
column.set(i % 3);
}
}
}
this.width = column.cardinality();
this.height = row.cardinality();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CustomDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id) {
throw new UnsupportedOperationException();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CustomDisplay> streamCodec() {
return CODEC;
}
}
@@ -0,0 +1,297 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.*;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
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.MinecraftServer;
import net.minecraft.tags.TagKey;
import net.minecraft.util.context.ContextMap;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.component.Fireworks;
import net.minecraft.world.item.component.ProvidesTrimMaterial;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.crafting.display.*;
import net.minecraft.world.item.equipment.trim.TrimMaterial;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.*;
import java.util.stream.Stream;
/**
* A display to be used alongside Roughly Enough Items.
* <p>
* see me.shedaniel.rei.api.common.display.Display
*/
public abstract class Display {
protected ResourceLocation id;
protected List<EntryIngredient> inputs;
protected List<EntryIngredient> outputs;
public Display(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation id) {
this.inputs = inputs;
this.outputs = outputs;
this.id = id;
}
@SuppressWarnings("unchecked")
public static StreamCodec<RegistryFriendlyByteBuf, Display> dispatchCodec() {
return new StreamCodec<>() {
@NotNull
@Override
public Display decode(@NotNull RegistryFriendlyByteBuf buffer) {
throw new UnsupportedOperationException();
}
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull Display display) {
new FriendlyByteBuf(buffer).writeResourceLocation(display.getSerializerId());
((StreamCodec<RegistryFriendlyByteBuf, Display>) display.streamCodec()).encode(buffer, display);
}
};
}
public static Collection<Display> ofTransmuteRecipe(@NotNull RecipeHolder<TransmuteRecipe> recipeHolder) {
TransmuteRecipe recipe = recipeHolder.value();
List<RecipeDisplay> displays = recipe.display();
List<Display> displayList = new ArrayList<>();
if (!displays.isEmpty()) {
RecipeDisplay recipeDisplay = displays.getFirst();
if (recipeDisplay instanceof ShapelessCraftingRecipeDisplay shapelessRecipeDisplay) {
displayList.add(new ShapelessDisplay(shapelessRecipeDisplay, recipeHolder.id().location()));
} else if (recipeDisplay instanceof ShapedCraftingRecipeDisplay shapelessRecipe) {
displayList.add(new ShapedDisplay(shapelessRecipe, recipeHolder.id().location()));
}
}
return displayList;
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.TippedArrowRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofTippedArrowRecipe(@NotNull RecipeHolder<TippedArrowRecipe> recipeHolder) {
EntryIngredient arrowIngredient = EntryIngredient.of(Items.ARROW);
Set<ResourceLocation> registeredPotions = new HashSet<>();
List<Display> displays = new ArrayList<>();
MinecraftServer.getServer().registryAccess().lookup(Registries.POTION).stream()
.flatMap(Registry::listElements)
.map(reference -> PotionContents.createItemStack(Items.LINGERING_POTION, reference))
.forEach(itemStack -> {
PotionContents potion = itemStack.get(DataComponents.POTION_CONTENTS);
if (potion == null || potion.potion().isEmpty()) {
return;
}
if (potion.potion().get().unwrapKey().isPresent() && registeredPotions.add(potion.potion().get().unwrapKey().get().location())) {
List<EntryIngredient> input = new ArrayList<>();
for (int i = 0; i < 4; i++) {
input.add(arrowIngredient);
}
input.add(EntryIngredient.of(itemStack));
for (int i = 0; i < 4; i++) {
input.add(arrowIngredient);
}
ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8);
outputStack.set(DataComponents.POTION_CONTENTS, potion);
displays.add(new CustomDisplay(input, List.of(EntryIngredient.of(outputStack)), recipeHolder.id().location()));
}
});
return displays;
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.TippedArrowRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofFireworkRocketRecipe(@NotNull RecipeHolder<FireworkRocketRecipe> recipeHolder) {
EntryIngredient[] inputs = new EntryIngredient[4];
inputs[0] = EntryIngredient.of(Items.GUNPOWDER);
inputs[1] = EntryIngredient.of(Items.PAPER);
inputs[2] = EntryIngredient.of(new ItemStack(Items.AIR), new ItemStack(Items.GUNPOWDER), new ItemStack(Items.GUNPOWDER));
inputs[3] = EntryIngredient.of(new ItemStack(Items.AIR), new ItemStack(Items.AIR), new ItemStack(Items.GUNPOWDER));
ItemStack[] outputs = new ItemStack[3];
for (int i = 0; i < 3; i++) {
outputs[i] = new ItemStack(Items.FIREWORK_ROCKET, 3);
outputs[i].set(DataComponents.FIREWORKS, new Fireworks(i + 1, List.of()));
}
return Collections.singleton(new ShapelessDisplay(List.of(inputs), List.of(EntryIngredient.of(outputs)), recipeHolder.id().location()));
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.MapCloningRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofMapCloningRecipe(@NotNull RecipeHolder<MapCloningRecipe> recipeHolder) {
return Collections.singleton(
new ShapelessDisplay(
List.of(EntryIngredient.of(Items.FILLED_MAP), EntryIngredient.of(Items.MAP)),
List.of(EntryIngredient.of(new ItemStack(Items.FILLED_MAP, 2))),
recipeHolder.id().location())
);
}
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay#ofTransforming
*/
@NotNull
public static SmithingDisplay ofTransforming(RecipeHolder<SmithingTransformRecipe> recipeHolder) {
return new SmithingDisplay(
List.of(
recipeHolder.value().templateIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty()),
EntryIngredient.ofIngredient(recipeHolder.value().baseIngredient()),
recipeHolder.value().additionIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty())
),
List.of(ofSlotDisplay(recipeHolder.value().getResult())),
SmithingDisplay.SmithingRecipeType.TRANSFORM,
recipeHolder.id().location()
);
}
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay#fromTrimming
*/
@SuppressWarnings("deprecation")
@NotNull
public static Collection<Display> ofSmithingTrimRecipe(@NotNull RecipeHolder<SmithingTrimRecipe> recipeHolder) {
RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess();
SmithingTrimRecipe recipe = recipeHolder.value();
List<Display> displays = new ArrayList<>();
for (Holder<Item> additionStack : (Iterable<Holder<Item>>) recipe.additionIngredient().map(Ingredient::items).orElse(Stream.of())::iterator) {
Holder<TrimMaterial> trimMaterial = getMaterialFromIngredient(registryAccess, additionStack).orElse(null);
if (trimMaterial == null) {
continue;
}
EntryIngredient baseIngredient = EntryIngredient.ofIngredient(recipe.baseIngredient());
displays.add(new SmithingDisplay.Trimming(List.of(
recipe.templateIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty()),
baseIngredient,
EntryIngredient.ofItemHolder(additionStack)
), List.of(baseIngredient), SmithingDisplay.SmithingRecipeType.TRIM, recipeHolder.id().location(), recipe.pattern()));
}
return displays;
}
private static Optional<Holder<TrimMaterial>> getMaterialFromIngredient(HolderLookup.Provider provider, Holder<Item> item) {
ProvidesTrimMaterial providesTrimMaterial = new ItemStack(item).get(DataComponents.PROVIDES_TRIM_MATERIAL);
return providesTrimMaterial != null ? providesTrimMaterial.unwrap(provider) : Optional.empty();
}
public static EntryIngredient ofSlotDisplay(SlotDisplay slot) {
return switch (slot) {
case SlotDisplay.Empty ignored -> EntryIngredient.empty();
case SlotDisplay.ItemSlotDisplay s -> EntryIngredient.of(s.item().value());
case SlotDisplay.ItemStackSlotDisplay s -> EntryIngredient.of(s.stack());
case SlotDisplay.TagSlotDisplay s -> ofItemTag(s.tag());
case SlotDisplay.Composite s -> {
ArrayList<ItemStack> list = new ArrayList<>();
for (SlotDisplay slotDisplay : s.contents()) {
ofSlotDisplay(slotDisplay).stream().forEach(list::add);
}
yield EntryIngredient.of(list.toArray(new ItemStack[0]));
}
// REI Bad idea
case SlotDisplay.AnyFuel ignored -> EntryIngredient.empty();
default -> {
RegistryAccess access = MinecraftServer.getServer().registryAccess();
try {
List<ItemStack> stacks = slot.resolveForStacks(new ContextMap.Builder()
.withParameter(SlotDisplayContext.REGISTRIES, access)
.create(SlotDisplayContext.CONTEXT));
yield EntryIngredient.of(stacks.toArray(new ItemStack[0]));
} catch (Exception e) {
MinecraftServer.LOGGER.warn("Failed to resolve slot display: {}", slot, e);
yield EntryIngredient.empty();
}
}
};
}
public static List<EntryIngredient> ofSlotDisplays(Collection<SlotDisplay> slots) {
if (slots instanceof Collection<?> collection && collection.isEmpty()) {
return Collections.emptyList();
}
ImmutableList.Builder<EntryIngredient> ingredients = ImmutableList.builder();
for (SlotDisplay slot : slots) {
ingredients.add(ofSlotDisplay(slot));
}
return ingredients.build();
}
public static <T extends ItemLike> EntryIngredient ofItemTag(TagKey<T> tagKey) {
HolderGetter<T> getter = MinecraftServer.getServer().registryAccess().lookupOrThrow(tagKey.registry());
HolderSet.Named<T> holders = getter.get(tagKey).orElse(null);
if (holders == null) {
return EntryIngredient.empty();
}
int size = holders.size();
if (size == 0) {
return EntryIngredient.empty();
}
if (size == 1) {
return EntryIngredient.of(new ItemStack(holders.get(0).value()));
}
List<ItemStack> stackList = new ArrayList<>();
for (Holder<T> t : holders) {
ItemStack stack = new ItemStack(t.value());
if (!stack.isEmpty()) {
stackList.add(stack);
}
}
return EntryIngredient.of(stackList.toArray(new ItemStack[0]));
}
public List<EntryIngredient> getInputEntries() {
return inputs;
}
public List<EntryIngredient> getOutputEntries() {
return outputs;
}
public ResourceLocation getDisplayLocation() {
return id;
}
public Optional<ResourceLocation> getOptionalLocation() {
return Optional.ofNullable(id);
}
public abstract ResourceLocation getSerializerId();
public abstract StreamCodec<RegistryFriendlyByteBuf, ? extends Display> streamCodec();
}
@@ -0,0 +1,112 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
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.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
/**
* see me.shedaniel.rei.plugin.common.displays.crafting.DefaultShapedDisplay#DefaultShapedDisplay(RecipeHolder)
*/
public class ShapedDisplay extends CraftingDisplay {
private static final StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CraftingDisplay::getOptionalLocation,
ByteBufCodecs.INT,
CraftingDisplay::getWidth,
ByteBufCodecs.INT,
CraftingDisplay::getHeight,
ShapedDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/shaped");
private final int width;
private final int height;
public ShapedDisplay(@NotNull RecipeHolder<ShapedRecipe> recipeHolder) {
super(
ofIngredient(recipeHolder.value()),
List.of(EntryIngredient.of(recipeHolder.value().assemble(CraftingInput.EMPTY, CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
this.width = recipeHolder.value().getWidth();
this.height = recipeHolder.value().getHeight();
}
public ShapedDisplay(@NotNull ShapedCraftingRecipeDisplay recipeDisplay, ResourceLocation id) {
super(
Display.ofSlotDisplays(recipeDisplay.ingredients()),
List.of(ofSlotDisplay(recipeDisplay.result())),
id
);
this.width = recipeDisplay.width();
this.height = recipeDisplay.height();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CraftingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<ResourceLocation> location, int width, int height) {
throw new UnsupportedOperationException();
}
private static List<EntryIngredient> ofIngredient(ShapedRecipe recipe) {
return recipe.getIngredients().stream().map(ingredient -> {
if (ingredient.isEmpty()) {
return EntryIngredient.empty();
}
ItemStack[] itemStacks = ingredient.get().items()
.map(itemHolder -> new ItemStack(itemHolder, 1))
.toArray(ItemStack[]::new);
return EntryIngredient.of(itemStacks);
}).toList();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> streamCodec() {
return CODEC;
}
}
@@ -0,0 +1,94 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
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.crafting.CraftingInput;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapelessRecipe;
import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay;
import org.apache.commons.lang3.NotImplementedException;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
public class ShapelessDisplay extends CraftingDisplay {
private static final StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CraftingDisplay::getOptionalLocation,
ShapelessDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/shapeless");
public ShapelessDisplay(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation location) {
super(inputs, outputs, location);
}
public ShapelessDisplay(@NotNull RecipeHolder<ShapelessRecipe> recipeHolder) {
this(
recipeHolder.value().placementInfo().ingredients().stream().map(EntryIngredient::ofIngredient).toList(),
List.of(EntryIngredient.of(recipeHolder.value().assemble(CraftingInput.EMPTY, CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
}
public ShapelessDisplay(@NotNull ShapelessCraftingRecipeDisplay recipeDisplay, ResourceLocation id) {
this(
ofSlotDisplays(recipeDisplay.ingredients()),
List.of(ofSlotDisplay(recipeDisplay.result())),
id
);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CraftingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<ResourceLocation> location) {
throw new NotImplementedException();
}
@Override
public int getWidth() {
return getInputEntries().size() > 4 ? 3 : 2;
}
@Override
public int getHeight() {
return getInputEntries().size() > 4 ? 3 : 2;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> streamCodec() {
return CODEC;
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SmeltingRecipe;
public class SmeltingDisplay extends CookingDisplay {
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smelting");
public SmeltingDisplay(RecipeHolder<SmeltingRecipe> recipe) {
super(recipe);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}
@@ -0,0 +1,141 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import com.mojang.serialization.Codec;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.Holder;
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.util.ByIdMap;
import net.minecraft.world.item.equipment.trim.TrimPattern;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
import java.util.function.IntFunction;
public class SmithingDisplay extends Display {
private static final StreamCodec<RegistryFriendlyByteBuf, SmithingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay::getOutputEntries,
ByteBufCodecs.optional(SmithingRecipeType.STREAM_CODEC),
SmithingDisplay::getOptionalType,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
SmithingDisplay::getOptionalLocation,
SmithingDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smithing");
private final SmithingRecipeType type;
public SmithingDisplay(
@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull SmithingRecipeType type,
@NotNull ResourceLocation location
) {
super(inputs, outputs, location);
this.type = type;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static SmithingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<SmithingRecipeType> type, Optional<ResourceLocation> location) {
throw new UnsupportedOperationException();
}
public Optional<SmithingRecipeType> getOptionalType() {
return Optional.of(type);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ? extends Display> streamCodec() {
return CODEC;
}
public enum SmithingRecipeType {
TRIM,
TRANSFORM,
;
public static final Codec<SmithingRecipeType> CODEC = Codec.STRING.xmap(SmithingRecipeType::valueOf, SmithingRecipeType::name);
public static final IntFunction<SmithingRecipeType> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
public static final StreamCodec<ByteBuf, SmithingRecipeType> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Enum::ordinal);
}
public static class Trimming extends SmithingDisplay {
private static final StreamCodec<RegistryFriendlyByteBuf, SmithingDisplay.Trimming> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay.Trimming::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay.Trimming::getOutputEntries,
ByteBufCodecs.optional(SmithingRecipeType.STREAM_CODEC),
SmithingDisplay.Trimming::getOptionalType,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
SmithingDisplay.Trimming::getOptionalLocation,
TrimPattern.STREAM_CODEC,
SmithingDisplay.Trimming::pattern,
SmithingDisplay.Trimming::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smithing/trimming");
private final Holder<TrimPattern> pattern;
public Trimming(
@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull SmithingRecipeType type,
@NotNull ResourceLocation location,
@NotNull Holder<TrimPattern> pattern
) {
super(inputs, outputs, type, location);
this.pattern = pattern;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Trimming of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<SmithingRecipeType> type, Optional<ResourceLocation> location, Holder<TrimPattern> pattern) {
throw new UnsupportedOperationException();
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ? extends Display> streamCodec() {
return CODEC;
}
public Holder<TrimPattern> pattern() {
return pattern;
}
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class SmokingDisplay extends CookingDisplay {
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smoking");
public SmokingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
super(recipe);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}
@@ -0,0 +1,77 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.display;
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.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import net.minecraft.world.item.crafting.StonecutterRecipe;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultStoneCuttingDisplay
*/
public class StoneCuttingDisplay extends Display {
private static final StreamCodec<RegistryFriendlyByteBuf, StoneCuttingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
StoneCuttingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
StoneCuttingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
StoneCuttingDisplay::getOptionalLocation,
StoneCuttingDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/stone_cutting");
public StoneCuttingDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation id) {
super(inputs, outputs, id);
}
public StoneCuttingDisplay(RecipeHolder<StonecutterRecipe> recipeHolder) {
this(
List.of(EntryIngredient.ofIngredient(recipeHolder.value().input())),
List.of(EntryIngredient.of(recipeHolder.value().assemble(new SingleRecipeInput(ItemStack.EMPTY), CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static StoneCuttingDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id) {
throw new UnsupportedOperationException();
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, StoneCuttingDisplay> streamCodec() {
return CODEC;
}
}
@@ -0,0 +1,116 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.ingredient;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
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.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
public class EntryIngredient {
private static final StreamCodec<RegistryFriendlyByteBuf, Holder<Item>> ITEM_STREAM_CODEC = ByteBufCodecs.holderRegistry(Registries.ITEM);
private static final ResourceLocation ITEM_ID = ResourceLocation.withDefaultNamespace("item");
public static final StreamCodec<RegistryFriendlyByteBuf, EntryIngredient> CODEC = new StreamCodec<>() {
@NotNull
@Override
public EntryIngredient decode(@NotNull RegistryFriendlyByteBuf buffer) {
throw new UnsupportedOperationException();
}
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull EntryIngredient value) {
ByteBufCodecs.writeCount(buffer, value.size(), Integer.MAX_VALUE);
value.stream().forEach(itemStack -> {
buffer.writeResourceLocation(ITEM_ID);
if (itemStack.isEmpty()) {
buffer.writeVarInt(0);
} else {
buffer.writeVarInt(itemStack.getCount());
ITEM_STREAM_CODEC.encode(buffer, itemStack.getItemHolder());
DataComponentPatch.STREAM_CODEC.encode(buffer, itemStack.components.asPatch());
}
});
}
};
private static final EntryIngredient EMPTY = new EntryIngredient(new ItemStack[0]);
@NotNull
private final ItemStack[] array;
private EntryIngredient(@NotNull ItemStack[] array) {
this.array = Objects.requireNonNull(array);
}
public static EntryIngredient empty() {
return EMPTY;
}
public static EntryIngredient ofItemHolder(@NotNull Holder<? extends ItemLike> item) {
return EntryIngredient.of(item.value());
}
public static EntryIngredient of(@NotNull ItemLike item) {
return EntryIngredient.of(new ItemStack(item));
}
public static EntryIngredient of(@NotNull ItemStack itemStack) {
return new EntryIngredient(new ItemStack[]{itemStack});
}
public static EntryIngredient of(@NotNull ItemStack... itemStacks) {
return new EntryIngredient(Arrays.copyOf(itemStacks, itemStacks.length));
}
@SuppressWarnings("deprecation")
public static EntryIngredient ofIngredient(Ingredient ingredient) {
if (ingredient.isEmpty()) {
return EntryIngredient.empty();
}
ItemStack[] itemStacks = ingredient.items()
.map(itemHolder -> itemHolder.value().getDefaultInstance())
.toArray(ItemStack[]::new);
return EntryIngredient.of(itemStacks);
}
public Stream<ItemStack> stream() {
return Arrays.stream(array);
}
public boolean isEmpty() {
return array.length == 0;
}
public ItemStack get(int index) {
return array[index].copy();
}
public int size() {
return array.length;
}
}
@@ -0,0 +1,93 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.payload;
import com.mojang.logging.LogUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.util.ByIdMap;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.display.Display;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
// This payload will never be sent to the client. We use PacketTransformer to send split payload.
public record DisplaySyncPayload(
SyncType syncType,
Collection<Display> displays,
long version
) implements LeavesCustomPayload {
private static final Logger LOGGER = LogUtils.getLogger();
public static final StreamCodec<? super RegistryFriendlyByteBuf, DisplaySyncPayload> STREAM_CODEC = StreamCodec.composite(
SyncType.STREAM_CODEC,
DisplaySyncPayload::syncType,
Display.dispatchCodec().apply(codec -> new StreamCodec<RegistryFriendlyByteBuf, Display>() {
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buf, @NotNull Display display) {
RegistryFriendlyByteBuf tmpBuf = new RegistryFriendlyByteBuf(Unpooled.buffer(), buf.registryAccess());
try {
codec.encode(tmpBuf, display);
} catch (Exception e) {
tmpBuf.release();
buf.writeBoolean(false);
LOGGER.warn("Failed to encode display: " + display, e);
return;
}
buf.writeBoolean(true);
RegistryFriendlyByteBuf.writeByteArray(buf, ByteBufUtil.getBytes(tmpBuf));
tmpBuf.release();
}
@NotNull
@Override
public Display decode(@NotNull RegistryFriendlyByteBuf buf) {
// The DisplayDecoder will not be called on the server side
throw new UnsupportedOperationException();
}
}
).apply(ByteBufCodecs.<RegistryFriendlyByteBuf, Display, Collection<Display>>collection(ArrayList::new)).map(
collection -> collection.stream().filter(Objects::nonNull).toList(),
UnaryOperator.identity()
),
DisplaySyncPayload::displays,
ByteBufCodecs.LONG,
DisplaySyncPayload::version,
DisplaySyncPayload::new
);
public enum SyncType {
APPEND,
SET;
public static final IntFunction<SyncType> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
public static final StreamCodec<ByteBuf, SyncType> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Enum::ordinal);
}
}
@@ -0,0 +1,176 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public abstract class InputSlotCrafter<T extends AbstractContainerMenu> {
protected T container;
private Iterable<SlotAccessor> inputStacks;
private Iterable<SlotAccessor> inventoryStacks;
protected ServerPlayer player;
protected InputSlotCrafter(T container) {
this.container = container;
}
public void fillInputSlots(ServerPlayer player, boolean hasShift) {
this.player = player;
this.inventoryStacks = this.getInventorySlots();
this.inputStacks = this.getInputSlots();
// Return the already placed items on the grid
this.cleanInputs();
ItemRecipeFinder recipeFinder = new ItemRecipeFinder();
this.populateRecipeFinder(recipeFinder);
List<List<ItemStack>> ingredients = new ArrayList<>(this.getInputs());
if (recipeFinder.findRecipe(ingredients, 1, null)) {
this.fillInputSlots(recipeFinder, ingredients, hasShift);
} else {
this.cleanInputs();
this.markDirty();
throw new NotEnoughMaterialsException();
}
this.markDirty();
}
protected abstract Iterable<SlotAccessor> getInputSlots();
protected abstract Iterable<SlotAccessor> getInventorySlots();
protected abstract List<List<ItemStack>> getInputs();
protected abstract void populateRecipeFinder(ItemRecipeFinder recipeFinder);
protected abstract void markDirty();
public void alignRecipeToGrid(Iterable<SlotAccessor> inputStacks, Iterator<ItemStack> recipeItems, int craftsAmount) {
for (SlotAccessor inputStack : inputStacks) {
if (!recipeItems.hasNext()) {
return;
}
this.acceptAlignedInput(recipeItems.next(), inputStack, craftsAmount);
}
}
public void acceptAlignedInput(ItemStack toBeTakenStack, SlotAccessor inputStack, int craftsAmount) {
if (!toBeTakenStack.isEmpty()) {
for (int i = 0; i < craftsAmount; ++i) {
this.fillInputSlot(inputStack, toBeTakenStack);
}
}
}
protected void fillInputSlot(SlotAccessor slot, ItemStack toBeTakenStack) {
SlotAccessor takenSlot = this.takeInventoryStack(toBeTakenStack);
if (takenSlot != null) {
ItemStack takenStack = takenSlot.getItemStack().copy();
if (!takenStack.isEmpty()) {
if (takenStack.getCount() > 1) {
takenSlot.takeStack(1);
} else {
takenSlot.setItemStack(ItemStack.EMPTY);
}
takenStack.setCount(1);
if (!slot.canPlace(takenStack)) {
return;
}
if (slot.getItemStack().isEmpty()) {
slot.setItemStack(takenStack);
} else {
slot.getItemStack().grow(1);
}
}
}
}
protected void fillInputSlots(ItemRecipeFinder recipeFinder, List<List<ItemStack>> ingredients, boolean hasShift) {
int recipeCrafts = recipeFinder.countRecipeCrafts(ingredients, Integer.MAX_VALUE, null);
int amountToFill = hasShift ? recipeCrafts : 1;
List<ItemStack> recipeItems = new ArrayList<>();
if (recipeFinder.findRecipe(ingredients, amountToFill, recipeItems::add)) {
int finalCraftsAmount = amountToFill;
for (ItemStack itemId : recipeItems) {
// Fix issue with empty item id (grid slot) [shift-click issue]
if (itemId.isEmpty()) {
continue;
}
finalCraftsAmount = Math.min(finalCraftsAmount, itemId.getMaxStackSize());
}
recipeItems.clear();
if (recipeFinder.findRecipe(ingredients, finalCraftsAmount, recipeItems::add)) {
this.cleanInputs();
this.alignRecipeToGrid(inputStacks, recipeItems.iterator(), finalCraftsAmount);
}
}
}
protected abstract void cleanInputs();
@Nullable
public SlotAccessor takeInventoryStack(ItemStack itemStack) {
boolean rejectedModification = false;
for (SlotAccessor inventoryStack : inventoryStacks) {
ItemStack itemStack1 = inventoryStack.getItemStack();
if (!itemStack1.isEmpty() && areItemsEqual(itemStack, itemStack1) && !itemStack1.isDamaged() && !itemStack1.isEnchanted() && !itemStack1.has(DataComponents.CUSTOM_NAME)) {
if (!inventoryStack.allowModification(player)) {
rejectedModification = true;
} else {
return inventoryStack;
}
}
}
if (rejectedModification) {
throw new IllegalStateException("Unable to take item from inventory due to slot not allowing modification! Item requested: " + itemStack);
}
return null;
}
private static boolean areItemsEqual(ItemStack stack1, ItemStack stack2) {
return ItemStack.isSameItemSameComponents(stack1, stack2);
}
public static class NotEnoughMaterialsException extends RuntimeException {
}
}
@@ -0,0 +1,126 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class ItemRecipeFinder {
private final Interner<ItemKey> keys = Interners.newWeakInterner();
private final RecipeFinder<ItemKey, Ingredient> finder = new RecipeFinder<>();
public boolean contains(ItemStack item) {
return finder.contains(ofKey(item));
}
public void take(ItemStack item, int amount) {
finder.take(ofKey(item), amount);
}
public void put(ItemStack item, int amount) {
finder.put(ofKey(item), amount);
}
public void addNormalItem(ItemStack itemStack) {
if (Inventory.isUsableForCrafting(itemStack)) {
this.addItem(itemStack);
}
}
public void addItem(ItemStack itemStack) {
this.addItem(itemStack, itemStack.getMaxStackSize());
}
public void addItem(ItemStack itemStack, int i) {
if (!itemStack.isEmpty()) {
int j = Math.min(i, itemStack.getCount());
this.finder.put(ofKey(itemStack), j);
}
}
public boolean findRecipe(List<List<ItemStack>> list, int maxCrafts, @Nullable Consumer<ItemStack> output) {
return finder.findRecipe(toIngredients(list), maxCrafts, flatten(itemStack -> {
if (output != null) {
output.accept(itemStack);
}
}));
}
public int countRecipeCrafts(List<List<ItemStack>> list, int maxCrafts, @Nullable Consumer<ItemStack> output) {
return finder.countRecipeCrafts(toIngredients(list), maxCrafts, flatten(itemStack -> {
if (output != null) {
output.accept(itemStack);
}
}));
}
private ItemKey ofKey(ItemStack itemStack) {
return keys.intern(new ItemKey(itemStack.getItemHolder(), itemStack.getComponentsPatch()));
}
private Ingredient ofKeys(int index, List<ItemStack> itemStack) {
return new Ingredient(index, itemStack.stream().map(this::ofKey).toList());
}
private List<Ingredient> toIngredients(List<List<ItemStack>> list) {
List<Ingredient> ingredients = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
List<ItemStack> stacks = list.get(i);
if (!stacks.isEmpty()) {
ingredients.add(ofKeys(i, stacks));
}
}
return ingredients;
}
private static BiConsumer<ItemKey, Ingredient> flatten(Consumer<ItemStack> consumer) {
int[] lastIndex = {-1};
return (itemKey, ingredient) -> {
for (int i = lastIndex[0] + 1; i < ingredient.index(); i++) {
consumer.accept(ItemStack.EMPTY);
}
consumer.accept(new ItemStack(itemKey.item(), 1, itemKey.patch()));
lastIndex[0] = ingredient.index();
};
}
private record Ingredient(int index, List<ItemKey> elements) implements RecipeFinder.Ingredient<ItemKey> {
}
private record ItemKey(Holder<Item> item, DataComponentPatch patch) {
}
}
@@ -0,0 +1,84 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import java.util.HashMap;
import java.util.List;
public class NewInputSlotCrafter<T extends AbstractContainerMenu> extends InputSlotCrafter<T> {
protected final List<SlotAccessor> inputSlots;
protected final List<SlotAccessor> inventorySlots;
protected final List<List<ItemStack>> inputs;
public NewInputSlotCrafter(T container, List<SlotAccessor> inputSlots, List<SlotAccessor> inventorySlots, List<List<ItemStack>> inputs) {
super(container);
this.inputSlots = inputSlots;
this.inventorySlots = inventorySlots;
this.inputs = inputs;
}
@Override
protected Iterable<SlotAccessor> getInputSlots() {
return this.inputSlots;
}
@Override
protected Iterable<SlotAccessor> getInventorySlots() {
return this.inventorySlots;
}
@Override
protected List<List<ItemStack>> getInputs() {
return this.inputs;
}
@Override
protected void populateRecipeFinder(ItemRecipeFinder recipeFinder) {
for (SlotAccessor slot : getInventorySlots()) {
recipeFinder.addNormalItem(slot.getItemStack());
}
}
@Override
protected void markDirty() {
player.getInventory().setChanged();
container.sendAllDataToRemote();
}
@Override
protected void cleanInputs() {
for (SlotAccessor slot : getInputSlots()) {
org.bukkit.inventory.ItemStack bukkitStack = slot.getItemStack().getBukkitStack();
if (bukkitStack.getType().isAir()) {
continue;
}
HashMap<Integer, org.bukkit.inventory.ItemStack> notAdded = player.getBukkitEntity().getInventory().addItem(bukkitStack);
if (notAdded.isEmpty()) {
slot.setItemStack(ItemStack.EMPTY);
} else {
org.bukkit.inventory.ItemStack remain = notAdded.values().iterator().next();
slot.setItemStack(ItemStack.fromBukkitCopy(remain));
throw new IllegalStateException("rei.rei.no.slot.in.inv");
}
}
}
}
@@ -0,0 +1,409 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
public class RecipeFinder<T, I extends RecipeFinder.Ingredient<T>> {
public final Reference2IntOpenHashMap<T> amounts = new Reference2IntOpenHashMap<>();
public boolean contains(T item) {
return this.amounts.getInt(item) > 0;
}
boolean containsAtLeast(T object, int i) {
return this.amounts.getInt(object) >= i;
}
public void take(T item, int amount) {
int taken = this.amounts.addTo(item, -amount);
if (taken < amount) {
throw new IllegalStateException("Took " + amount + " items, but only had " + taken);
}
}
public void put(T item, int amount) {
this.amounts.addTo(item, amount);
}
public boolean findRecipe(List<I> list, int maxCrafts, @Nullable BiConsumer<T, I> output) {
return new Filter(list).tryPick(maxCrafts, output);
}
public int countRecipeCrafts(List<I> list, int maxCrafts, @Nullable BiConsumer<T, I> output) {
return new Filter(list).tryPickAll(maxCrafts, output);
}
public void clear() {
this.amounts.clear();
}
class Filter {
private final List<I> ingredients;
private final int ingredientCount;
private final List<T> items;
private final int itemCount;
private final BitSet data;
private final IntList path = new IntArrayList();
public Filter(final List<I> list) {
this.ingredients = list;
this.ingredientCount = this.ingredients.size();
this.items = this.getUniqueAvailableIngredientItems();
this.itemCount = this.items.size();
this.data = new BitSet(this.visitedIngredientCount() + this.visitedItemCount() + this.satisfiedCount() + this.connectionCount() + this.residualCount());
this.setInitialConnections();
}
private void setInitialConnections() {
for (int i = 0; i < this.ingredientCount; i++) {
List<T> list = this.ingredients.get(i).elements();
for (int j = 0; j < this.itemCount; j++) {
if (list.contains(this.items.get(j))) {
this.setConnection(j, i);
}
}
}
}
public boolean tryPick(int maxCrafts, @Nullable BiConsumer<T, I> output) {
if (maxCrafts <= 0) {
return true;
} else {
int j = 0;
while (true) {
IntList intList = this.tryAssigningNewItem(maxCrafts);
if (intList == null) {
boolean bl = j == this.ingredientCount;
boolean bl2 = bl && output != null;
this.clearAllVisited();
this.clearSatisfied();
for (int l = 0; l < this.ingredientCount; l++) {
for (int m = 0; m < this.itemCount; m++) {
if (this.isAssigned(m, l)) {
this.unassign(m, l);
put(this.items.get(m), maxCrafts);
if (bl2) {
output.accept(this.items.get(m), this.ingredients.get(l));
}
break;
}
}
}
assert this.data.get(this.residualOffset(), this.residualOffset() + this.residualCount()).isEmpty();
return bl;
}
int k = intList.getInt(0);
take(this.items.get(k), maxCrafts);
int l = intList.size() - 1;
this.setSatisfied(intList.getInt(l));
j++;
for (int mx = 0; mx < intList.size() - 1; mx++) {
if (isPathIndexItem(mx)) {
int n = intList.getInt(mx);
int o = intList.getInt(mx + 1);
this.assign(n, o);
} else {
int n = intList.getInt(mx + 1);
int o = intList.getInt(mx);
this.unassign(n, o);
}
}
}
}
}
private static boolean isPathIndexItem(int i) {
return (i & 1) == 0;
}
private List<T> getUniqueAvailableIngredientItems() {
Set<T> set = new ReferenceOpenHashSet<>();
for (Ingredient<T> ingredient : this.ingredients) {
set.addAll(ingredient.elements());
}
set.removeIf(object -> !contains(object));
return List.copyOf(set);
}
@Nullable
private IntList tryAssigningNewItem(int i) {
this.clearAllVisited();
for (int j = 0; j < this.itemCount; j++) {
if (containsAtLeast(this.items.get(j), i)) {
IntList intList = this.findNewItemAssignmentPath(j);
if (intList != null) {
return intList;
}
}
}
return null;
}
@Nullable
private IntList findNewItemAssignmentPath(int i) {
this.path.clear();
this.visitItem(i);
this.path.add(i);
while (!this.path.isEmpty()) {
int j = this.path.size();
int k = this.path.getInt(j - 1);
if (isPathIndexItem(j - 1)) {
for (int l = 0; l < this.ingredientCount; l++) {
if (!this.hasVisitedIngredient(l) && this.hasConnection(k, l) && !this.isAssigned(k, l)) {
this.visitIngredient(l);
this.path.add(l);
break;
}
}
} else {
if (!this.isSatisfied(k)) {
return this.path;
}
for (int lx = 0; lx < this.itemCount; lx++) {
if (!this.hasVisitedItem(lx) && this.isAssigned(lx, k)) {
assert this.hasConnection(lx, k);
this.visitItem(lx);
this.path.add(lx);
break;
}
}
}
int l = this.path.size();
if (l == j) {
this.path.removeInt(l - 1);
}
}
return null;
}
private int visitedIngredientOffset() {
return 0;
}
private int visitedIngredientCount() {
return this.ingredientCount;
}
private int visitedItemOffset() {
return this.visitedIngredientOffset() + this.visitedIngredientCount();
}
private int visitedItemCount() {
return this.itemCount;
}
private int satisfiedOffset() {
return this.visitedItemOffset() + this.visitedItemCount();
}
private int satisfiedCount() {
return this.ingredientCount;
}
private int connectionOffset() {
return this.satisfiedOffset() + this.satisfiedCount();
}
private int connectionCount() {
return this.ingredientCount * this.itemCount;
}
private int residualOffset() {
return this.connectionOffset() + this.connectionCount();
}
private int residualCount() {
return this.ingredientCount * this.itemCount;
}
private boolean isSatisfied(int i) {
return this.data.get(this.getSatisfiedIndex(i));
}
private void setSatisfied(int i) {
this.data.set(this.getSatisfiedIndex(i));
}
private int getSatisfiedIndex(int i) {
assert i >= 0 && i < this.ingredientCount;
return this.satisfiedOffset() + i;
}
private void clearSatisfied() {
this.clearRange(this.satisfiedOffset(), this.satisfiedCount());
}
private void setConnection(int i, int j) {
this.data.set(this.getConnectionIndex(i, j));
}
private boolean hasConnection(int i, int j) {
return this.data.get(this.getConnectionIndex(i, j));
}
private int getConnectionIndex(int i, int j) {
assert i >= 0 && i < this.itemCount;
assert j >= 0 && j < this.ingredientCount;
return this.connectionOffset() + i * this.ingredientCount + j;
}
private boolean isAssigned(int i, int j) {
return this.data.get(this.getResidualIndex(i, j));
}
private void assign(int i, int j) {
int k = this.getResidualIndex(i, j);
assert !this.data.get(k);
this.data.set(k);
}
private void unassign(int i, int j) {
int k = this.getResidualIndex(i, j);
assert this.data.get(k);
this.data.clear(k);
}
private int getResidualIndex(int i, int j) {
assert i >= 0 && i < this.itemCount;
assert j >= 0 && j < this.ingredientCount;
return this.residualOffset() + i * this.ingredientCount + j;
}
private void visitIngredient(int i) {
this.data.set(this.getVisitedIngredientIndex(i));
}
private boolean hasVisitedIngredient(int i) {
return this.data.get(this.getVisitedIngredientIndex(i));
}
private int getVisitedIngredientIndex(int i) {
assert i >= 0 && i < this.ingredientCount;
return this.visitedIngredientOffset() + i;
}
private void visitItem(int i) {
this.data.set(this.getVisitiedItemIndex(i));
}
private boolean hasVisitedItem(int i) {
return this.data.get(this.getVisitiedItemIndex(i));
}
private int getVisitiedItemIndex(int i) {
assert i >= 0 && i < this.itemCount;
return this.visitedItemOffset() + i;
}
private void clearAllVisited() {
this.clearRange(this.visitedIngredientOffset(), this.visitedIngredientCount());
this.clearRange(this.visitedItemOffset(), this.visitedItemCount());
}
private void clearRange(int i, int j) {
this.data.clear(i, i + j);
}
public int tryPickAll(int i, @Nullable BiConsumer<T, I> output) {
int j = 0;
int k = Math.min(i, this.getMinIngredientCount()) + 1;
while (true) {
int l = (j + k) / 2;
if (this.tryPick(l, null)) {
if (k - j <= 1) {
if (l > 0) {
this.tryPick(l, output);
}
return l;
}
j = l;
} else {
k = l;
}
}
}
private int getMinIngredientCount() {
int i = Integer.MAX_VALUE;
for (Ingredient<T> ingredient : this.ingredients) {
int j = 0;
for (T object : ingredient.elements()) {
j = Math.max(j, amounts.getInt(object));
}
if (i > 0) {
i = Math.min(i, j);
}
}
return i;
}
}
public interface Ingredient<T> {
List<T> elements();
}
}
@@ -0,0 +1,56 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
public class PlayerInventorySlotAccessor implements SlotAccessor {
protected Player player;
protected int index;
public PlayerInventorySlotAccessor(Player player, int index) {
this.player = player;
this.index = index;
}
@Override
public ItemStack getItemStack() {
return player.getInventory().getItem(index);
}
@Override
public void setItemStack(ItemStack stack) {
this.player.getInventory().setItem(index, stack);
}
@Override
public void takeStack(int amount) {
this.player.getInventory().removeItem(index, amount);
}
public int getIndex() {
return index;
}
}
@@ -0,0 +1,43 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
public interface SlotAccessor {
ItemStack getItemStack();
void setItemStack(ItemStack stack);
void takeStack(int amount);
default boolean allowModification(Player player) {
return true;
}
default boolean canPlace(ItemStack stack) {
return true;
}
}
@@ -0,0 +1,65 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public class VanillaSlotAccessor implements SlotAccessor {
protected Slot slot;
public VanillaSlotAccessor(Slot slot) {
this.slot = slot;
}
@Override
public ItemStack getItemStack() {
return slot.getItem();
}
@Override
public void setItemStack(ItemStack stack) {
this.slot.set(stack);
}
@Override
public void takeStack(int amount) {
slot.remove(amount);
}
public Slot getSlot() {
return slot;
}
@Override
public boolean allowModification(Player player) {
return slot.allowModification(player);
}
@Override
public boolean canPlace(ItemStack stack) {
return slot.mayPlace(stack);
}
}
@@ -18,7 +18,7 @@
package org.leavesmc.leaves.protocol.servux;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.ServuxProtocolConfig;
import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig;
import io.netty.buffer.Unpooled;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
@@ -20,7 +20,7 @@ package org.leavesmc.leaves.protocol.servux;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.mojang.serialization.DataResult;
import fun.bm.lophine.config.modules.function.ServuxProtocolConfig;
import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
@@ -18,7 +18,7 @@
package org.leavesmc.leaves.protocol.servux;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.ServuxProtocolConfig;
import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -17,7 +17,7 @@
package org.leavesmc.leaves.protocol.servux.litematics;
import fun.bm.lophine.config.modules.function.ServuxProtocolConfig;
import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -18,7 +18,7 @@
package org.leavesmc.leaves.protocol.servux.litematics.placement;
import com.google.common.collect.ImmutableMap;
import fun.bm.lophine.config.modules.function.ServuxProtocolConfig;
import fun.bm.lophine.config.modules.function.protocol.ServuxProtocolConfig;
import io.papermc.paper.threadedregions.RegionizedServer;
import me.earthme.luminol.utils.NullPlugin;
import net.kyori.adventure.text.Component;
@@ -0,0 +1,396 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.syncmatica;
import com.mojang.authlib.GameProfile;
import fun.bm.lophine.config.modules.function.protocol.SyncmaticaProtocolConfig;
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.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.syncmatica.exchange.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@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 SyncmaticaProtocolConfig.enabled;
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,81 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.syncmatica;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
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);
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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");
}
}
@@ -0,0 +1,41 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -0,0 +1,25 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol.syncmatica;
public enum MessageType {
SUCCESS,
INFO,
WARNING,
ERROR
}

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