diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPacketHandler.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPacketHandler.java new file mode 100644 index 0000000000..007d9760a1 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPacketHandler.java @@ -0,0 +1,16 @@ +package vg.civcraft.mc.civmodcore.mods.packets; + +import com.google.gson.JsonElement; +import net.kyori.adventure.key.Key; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public interface CivModPacketHandler { + public static final CivModPacketHandler NOOP = (sender, packetId, json) -> {}; + + public void handleCivModPacket( + @NotNull Player sender, + @NotNull Key packetId, + @NotNull JsonElement json + ); +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPackets.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPackets.java new file mode 100644 index 0000000000..d4b06b0e48 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/mods/packets/CivModPackets.java @@ -0,0 +1,125 @@ +package vg.civcraft.mc.civmodcore.mods.packets; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import net.kyori.adventure.key.Key; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.messaging.Messenger; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CivModPackets { + private static final Logger LOGGER = LoggerFactory.getLogger(CivModPackets.class); + private static final Gson GSON = new Gson(); + + public static void registerPacket( + final @NotNull JavaPlugin plugin, + final @NotNull Key packetId, + final boolean registerIncoming, + final boolean registerOutgoing, + final @NotNull CivModPacketHandler handler + ) { + Objects.requireNonNull(plugin); + Objects.requireNonNull(packetId); + Objects.requireNonNull(handler); + final Messenger messenger = Bukkit.getMessenger(); + if (registerIncoming) { + messenger.registerIncomingPluginChannel(plugin, packetId.toString(), (channel, sender, payload) -> { + final Key receivedPacketId; + try { + receivedPacketId = Key.key(channel); + } + catch (final Exception e) { + LOGGER.warn( + "{} sent a packet with an invalid packetId: {}", + sender.getName(), + channel, + e + ); + return; + } + final JsonElement json; { + final String raw = new String(payload, StandardCharsets.UTF_8); + try { + json = GSON.fromJson(raw, JsonElement.class); + } + catch (final Exception e) { + LOGGER.warn( + "{} sent a packet [packetId:{}] with an invalid payload: {}", + sender.getName(), + receivedPacketId, + raw, + e + ); + return; + } + } + LOGGER.info( + "{} sent packet [packetId:{}]: {}", + sender.getName(), + receivedPacketId, + json + ); + try { + handler.handleCivModPacket(sender, receivedPacketId, json); + } + catch (final Exception e) { + LOGGER.warn( + "Something went wrong while handling {}'s [packetId:{}] with payload: {}", + sender.getName(), + receivedPacketId, + json, + e + ); + } + }); + } + if (registerOutgoing) { + messenger.registerOutgoingPluginChannel(plugin, packetId.toString()); + } + } + + /// Use this to register incoming-only packets. If the packet is bidirectional, use [#registerPacket] instead. + public static void registerIncomingPacket( + final @NotNull JavaPlugin plugin, + final @NotNull Key packetId, + final @NotNull CivModPacketHandler handler + ) { + registerPacket(plugin, packetId, true, false, handler); + } + + /// Use this to register outgoing-only packets. If the packet is bidirectional, use [#registerPacket] instead. + public static void registerOutgoingPacket( + final @NotNull JavaPlugin plugin, + final @NotNull Key packetId + ) { + registerPacket(plugin, packetId, false, true, CivModPacketHandler.NOOP); + } + + public static void sendPacket( + final @NotNull JavaPlugin plugin, + final @NotNull Player recipient, + final @NotNull Key packetId, + final @NotNull JsonElement json + ) { + final String payload = GSON.toJson(json); + LOGGER.info( + "Sending packet to {}: [packetId:{}]: {}", + recipient.getName(), + packetId, + payload + ); + recipient.sendPluginMessage( + plugin, + packetId.toString(), + payload.getBytes(StandardCharsets.UTF_8) + ); + } +} + + diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/KeyedUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/KeyedUtils.java index 798f55d766..bdaf8deb1a 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/KeyedUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/KeyedUtils.java @@ -1,7 +1,9 @@ package vg.civcraft.mc.civmodcore.utilities; +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; import org.bukkit.Keyed; import org.bukkit.NamespacedKey; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,4 +72,27 @@ public static NamespacedKey testKey(@NotNull final String key) { return new NamespacedKey("test", key); } + public static @NotNull NamespacedKey of( + final @NotNull Class pluginClass, + final @NotNull String value + ) { + /// This code is based on [JavaPlugin#getPlugin] but avoids accessing the plugin instance itself, making this + /// safe to use even when a plugin instance doesn't necessarily exist yet. + if (!JavaPlugin.class.isAssignableFrom(pluginClass)) { + throw new IllegalArgumentException("%s does not extend %s!".formatted( + pluginClass.getName(), + JavaPlugin.class.getName() + )); + } + if (!(pluginClass.getClassLoader() instanceof ConfiguredPluginClassLoader classLoader)) { + throw new IllegalArgumentException("%s was not loaded by %s!".formatted( + pluginClass.getName(), + ConfiguredPluginClassLoader.class.getName() + )); + } + return new NamespacedKey( + classLoader.getConfiguration().namespace(), + value + ); + } } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/ItemExchangePlugin.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/ItemExchangePlugin.java index 24c3ee4694..63cae7e59e 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/ItemExchangePlugin.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/ItemExchangePlugin.java @@ -5,6 +5,7 @@ import com.untamedears.itemexchange.glues.jukealert.JukeAlertGlue; import com.untamedears.itemexchange.glues.namelayer.NameLayerGlue; import com.untamedears.itemexchange.rules.ModifierRegistrar; +import com.untamedears.itemexchange.rules.ShopRule; import com.untamedears.itemexchange.rules.modifiers.BookModifier; import com.untamedears.itemexchange.rules.modifiers.DamageableModifier; import com.untamedears.itemexchange.rules.modifiers.DisplayNameModifier; @@ -16,6 +17,7 @@ import java.util.List; import vg.civcraft.mc.civmodcore.ACivMod; import vg.civcraft.mc.civmodcore.commands.CommandManager; +import vg.civcraft.mc.civmodcore.mods.packets.CivModPackets; import vg.civcraft.mc.civmodcore.utilities.DependencyGlue; /** @@ -54,6 +56,7 @@ public void onEnable() { modifiers.registerModifier(BookModifier.TEMPLATE); // 1000 registerListener(new ItemExchangeListener()); this.glues.forEach(DependencyGlue::registerGlue); + CivModPackets.registerOutgoingPacket(this, ShopRule.SHOW_TRADE_PACKET); } @Override diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java index 7667443a94..0c0555a9fb 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java @@ -33,10 +33,8 @@ public final class GroupModifier extends ModifierData { // Make sure to have a template instance to make registration easier public static final GroupModifier TEMPLATE = new GroupModifier(); - private static final String ID_KEY = "id"; private static final String NAME_KEY = "name"; - private int groupId; private String groupName; @Override @@ -51,18 +49,18 @@ public boolean conforms(final ItemStack item) { @Override public boolean isBroken() { - return false; + return this.groupName == null; } @Override public void toNBT(final @NotNull NbtCompound nbt) { - nbt.setInt(ID_KEY, getGroupId()); - nbt.setString(NAME_KEY, getGroupName()); + if (this.groupName instanceof final String groupName) { + nbt.setString(NAME_KEY, groupName); + } } public static @NotNull GroupModifier fromNBT(final @NotNull NbtCompound nbt) { final var modifier = new GroupModifier(); - modifier.setGroupId(nbt.getInt(ID_KEY, 0)); modifier.setGroupName(nbt.getString(NAME_KEY, null)); return modifier; } @@ -76,7 +74,7 @@ public List getDisplayInfo() { @Override public String toString() { - return "%S{%s:%d}".formatted(getSlug(), getGroupName(), getGroupId()); + return "%S:%s".formatted(getSlug(), getGroupName()); } // ------------------------------------------------------------ @@ -104,7 +102,6 @@ public void commandSetGroup(final Player player, return; } final GroupModifier modifier = handler.ensureModifier(); - modifier.setGroupId(group.getGroupId()); modifier.setGroupName(group.getName()); handler.relay(ChatColor.GREEN + "Set trade to group \"" + group.getName() + "\""); } @@ -114,14 +111,6 @@ public void commandSetGroup(final Player player, // Getters + Setters // ------------------------------------------------------------ - public int getGroupId() { - return this.groupId; - } - - public void setGroupId(final int groupId) { - this.groupId = groupId; - } - public String getGroupName() { return this.groupName; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/NameLayerGlue.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/NameLayerGlue.java index 3e1cb11da5..0a6f07f139 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/NameLayerGlue.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/NameLayerGlue.java @@ -25,7 +25,7 @@ public void denyPurchaseIfNotGotPerms(final BrowseOrPurchaseEvent event) { final GroupModifier modifier = event.getTrade().getInput().getModifiers().get(GroupModifier.class); if (!Validation.checkValidity(modifier) || PermissionsGlue.PURCHASE_PERMISSION.testPermission( - GroupManager.getGroup(modifier.getGroupId()), event.getBrowser())) { + GroupManager.getGroup(modifier.getGroupName()), event.getBrowser())) { return; } event.limitToBrowsing(); diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ShopRule.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ShopRule.java index 333b3c78cf..fd12147651 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ShopRule.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ShopRule.java @@ -4,14 +4,17 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.mojang.serialization.JsonOps; import com.untamedears.itemexchange.ItemExchangeConfig; import com.untamedears.itemexchange.ItemExchangePlugin; import com.untamedears.itemexchange.events.BlockInventoryRequestEvent; import java.util.ArrayList; import java.util.List; import java.util.Set; +import net.minecraft.nbt.NbtOps; import org.apache.commons.collections4.CollectionUtils; import org.bukkit.ChatColor; +import org.bukkit.NamespacedKey; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; @@ -19,6 +22,9 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.util.BlockIterator; import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; +import vg.civcraft.mc.civmodcore.mods.packets.CivModPackets; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; +import vg.civcraft.mc.civmodcore.utilities.KeyedUtils; import vg.civcraft.mc.civmodcore.utilities.Validation; import vg.civcraft.mc.civmodcore.world.WorldUtils; @@ -28,6 +34,7 @@ public final class ShopRule implements Validation { private final ItemExchangePlugin PLUGIN = ItemExchangePlugin.getInstance(); + public static final NamespacedKey SHOW_TRADE_PACKET = KeyedUtils.of(ItemExchangePlugin.class, "trade-details"); private final List trades = new ArrayList<>(); @@ -84,19 +91,36 @@ public void presentShopToPlayer(Player player) { if (trade == null) { throw new NullPointerException("Could not message player about trade... this shouldn't happen."); } + final int currentTradeIndex = this.currentTradeIndex + 1, tradesCount = this.trades.size(); player.sendMessage(String.format("%s(%d/%d) exchanges present.", - ChatColor.YELLOW, this.currentTradeIndex + 1, this.trades.size())); + ChatColor.YELLOW, currentTradeIndex, tradesCount)); for (String line : trade.getInput().getDisplayInfo()) { player.sendMessage(line); } - if (trade.getOutput() != null) { - for (String line : trade.getOutput().getDisplayInfo()) { + int stock = 0; + if (trade.getOutput() instanceof final ExchangeRule outputRule) { + for (String line : outputRule.getDisplayInfo()) { player.sendMessage(line); } PLUGIN.debug("[ShopRule] Calculating stock."); - int stock = trade.calculateStock(); + stock = trade.calculateStock(); player.sendMessage(ChatColor.YELLOW + "" + stock + " exchange" + (stock == 1 ? "" : "s") + " available."); } + // Mod support + final var displayNbt = new NbtCompound(); + displayNbt.setInt("trades", tradesCount); + displayNbt.setInt("index", currentTradeIndex); + displayNbt.setCompound("input", trade.getInput().toNBT()); + if (trade.getOutput() instanceof final ExchangeRule outputRule) { + displayNbt.setCompound("output", outputRule.toNBT()); + displayNbt.setInt("stock", stock); + } + CivModPackets.sendPacket( + ItemExchangePlugin.getInstance(), + player, + SHOW_TRADE_PACKET, + NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, displayNbt.internal()) + ); } // ------------------------------------------------------------ diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java index aec178c806..c5f029dd2e 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java @@ -6,6 +6,12 @@ @Deprecated public interface NBTSerializable { + default @NotNull NbtCompound toNBT() { + final var nbt = new NbtCompound(); + this.toNBT(nbt); + return nbt; + } + /** * Serializes this class onto a given NBTCompound. *