Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ subprojects {
maven("https://repo.negative.games/repository/maven-releases/")
maven("https://repo.negative.games/repository/maven-snapshots/")

// CodeMC
maven("https://repo.codemc.io/repository/maven-releases/")
maven("https://repo.codemc.io/repository/maven-snapshots/")

// PaperMC
maven("https://repo.papermc.io/repository/maven-public/")

Expand Down Expand Up @@ -47,4 +51,4 @@ subprojects {
}
}
}
}
}
Empty file modified gradlew
100644 → 100755
Empty file.
7 changes: 5 additions & 2 deletions paper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

var id = "plugin-engine-paper"
var domain = "games.negative.engine"
var apiVersion = "1.1.0"
var apiVersion = "1.2.0"

repositories {
mavenCentral()
Expand All @@ -29,6 +29,9 @@ dependencies {
// PlaceholderAPI
compileOnly("me.clip:placeholderapi:2.11.7")

// PacketEvents
compileOnly("com.github.retrooper:packetevents-spigot:2.11.2")

// Spring & Jakarta
compileOnly("org.springframework:spring-context:6.2.13")
compileOnly("jakarta.annotation:jakarta.annotation-api:3.0.0")
Expand Down Expand Up @@ -89,4 +92,4 @@ publishing {
}
}
}
}
}
16 changes: 16 additions & 0 deletions paper/src/main/java/games/negative/engine/paper/PaperPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import games.negative.engine.Plugin;
import games.negative.engine.message.util.MiniMessageUtil;
import games.negative.engine.paper.event.ClientItemLorePacketBridge;
import games.negative.engine.paper.scheduler.Scheduler;
import games.negative.engine.state.Reloadable;
import games.negative.moss.paper.MossPaper;
Expand All @@ -20,6 +21,8 @@
@Slf4j
public abstract class PaperPlugin extends MossPaper implements Plugin {

private ClientItemLorePacketBridge clientItemLorePacketBridge;

@Override
public void loadInitialComponents(AnnotationConfigApplicationContext context) {
super.loadInitialComponents(context);
Expand All @@ -37,6 +40,19 @@ public void onEnable() {
listener -> getServer().getPluginManager().registerEvents(listener, this),
(listener, e) -> log.error("Failed to register listener: {}", listener.getClass().getSimpleName(), e)
);

this.clientItemLorePacketBridge = new ClientItemLorePacketBridge(this);
this.clientItemLorePacketBridge.enable();
Comment thread
ericlmao marked this conversation as resolved.
}

@Override
public void onDisable() {
if (this.clientItemLorePacketBridge != null) {
this.clientItemLorePacketBridge.disable();
this.clientItemLorePacketBridge = null;
}

super.onDisable();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package games.negative.engine.paper.event;

public interface ClientItemLoreBridge {

void enable();

void disable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package games.negative.engine.paper.event;

import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import java.util.ArrayList;
import java.util.List;

/**
* Fired when a client-bound item packet is about to be sent to a player.
* Changes made through this event only affect the client view of the item lore.
*/
public final class ClientItemLoreEvent extends PlayerEvent {

private static final HandlerList HANDLERS = new HandlerList();

private final int windowId;
private final int slot;
private final ItemStack originalItem;
private List<Component> lore;
private boolean modified;

public ClientItemLoreEvent(Player player, int windowId, int slot, ItemStack item) {
super(player);
this.windowId = windowId;
this.slot = slot;
this.originalItem = item;

ItemMeta meta = item.getItemMeta();
List<Component> currentLore = meta == null ? List.of() : meta.lore();
this.lore = currentLore == null ? new ArrayList<>() : new ArrayList<>(currentLore);
}

public int getWindowId() {
return windowId;
}

public int getSlot() {
return slot;
}

public ItemStack getOriginalItem() {
return originalItem.clone();
}

/**
* Returns an immutable snapshot of the current client-side lore.
* Use {@link #setLore(List)}, {@link #addLoreLine(Component)}, or {@link #clearLore()} to change it.
*/
public List<Component> getLore() {
return List.copyOf(lore);
}

public void setLore(List<Component> lore) {
this.lore = lore == null ? new ArrayList<>() : new ArrayList<>(lore);
this.modified = true;
}

public void addLoreLine(Component line) {
this.lore.add(line);
this.modified = true;
}

public void clearLore() {
this.lore.clear();
this.modified = true;
}

boolean isModified() {
return modified;
}

@Override
public HandlerList getHandlers() {
return HANDLERS;
}

public static HandlerList getHandlerList() {
return HANDLERS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package games.negative.engine.paper.event;

import lombok.extern.slf4j.Slf4j;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;

/**
* Optional loader for PacketEvents-backed client item lore handling.
*/
@Slf4j
public final class ClientItemLorePacketBridge {

private static final String[] PACKET_EVENTS_PLUGIN_NAMES = {"packetevents", "PacketEvents"};

private final Plugin plugin;
private ClientItemLoreBridge delegate;

public ClientItemLorePacketBridge(Plugin plugin) {
this.plugin = plugin;
}

public void enable() {
if (delegate != null) return;
if (!isPacketEventsPresent()) {
log.info("PacketEvents not present; client item lore bridge will remain disabled");
return;
}

try {
ClientItemLoreBridge bridge = new PacketEventsClientItemLorePacketBridge(plugin);
bridge.enable();
this.delegate = bridge;
} catch (NoClassDefFoundError exception) {
log.warn("Failed to initialize optional PacketEvents client item lore bridge; runtime classes are unavailable", exception);
} catch (LinkageError exception) {
log.warn("Failed to initialize PacketEvents client item lore bridge due to a linkage problem", exception);
}
}

public void disable() {
if (delegate == null) return;

delegate.disable();
delegate = null;
}

private boolean isPacketEventsPresent() {
return getPacketEventsPlugin() != null;
}

private Plugin getPacketEventsPlugin() {
PluginManager pluginManager = plugin.getServer().getPluginManager();
for (String pluginName : PACKET_EVENTS_PLUGIN_NAMES) {
Plugin packetEventsPlugin = pluginManager.getPlugin(pluginName);
if (packetEventsPlugin != null) {
return packetEventsPlugin;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package games.negative.engine.paper.event;

import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketListenerCommon;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetSlot;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWindowItems;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.Plugin;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* PacketEvents-specific bridge that fires {@link ClientItemLoreEvent} for outgoing item packets.
*/
@Slf4j
public final class PacketEventsClientItemLorePacketBridge extends PacketListenerAbstract implements ClientItemLoreBridge {

private static final int CARRIED_ITEM_SLOT = -1;

private final Plugin plugin;
private PacketListenerCommon registeredListener;

public PacketEventsClientItemLorePacketBridge(Plugin plugin) {
super();
this.plugin = plugin;
}

@Override
public void enable() {
if (registeredListener != null) return;
if (PacketEvents.getAPI() == null) {
log.warn("PacketEvents API is unavailable; client item lore bridge was not registered");
return;
}

this.registeredListener = PacketEvents.getAPI().getEventManager().registerListener(this);
log.info("Enabled PacketEvents client item lore bridge");
}

@Override
public void disable() {
if (registeredListener == null) return;
if (PacketEvents.getAPI() == null) {
this.registeredListener = null;
return;
}

PacketEvents.getAPI().getEventManager().unregisterListener(registeredListener);
this.registeredListener = null;
}

@Override
public void onPacketSend(PacketSendEvent event) {
if (!(event.getPlayer() instanceof Player player)) return;

if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) {
handleSetSlot(event, player);
return;
}

if (event.getPacketType() == PacketType.Play.Server.WINDOW_ITEMS) {
handleWindowItems(event, player);
}
}

private void handleSetSlot(PacketSendEvent event, Player player) {
WrapperPlayServerSetSlot wrapper = new WrapperPlayServerSetSlot(event);
ItemStack updatedItem = applyLore(
player,
wrapper.getWindowId(),
wrapper.getSlot(),
wrapper.getItem()
);

if (updatedItem != null) {
wrapper.setItem(updatedItem);
}
}

private void handleWindowItems(PacketSendEvent event, Player player) {
WrapperPlayServerWindowItems wrapper = new WrapperPlayServerWindowItems(event);

List<ItemStack> items = wrapper.getItems();
List<ItemStack> modifiedItems = new ArrayList<>(items.size());
for (int slot = 0; slot < items.size(); slot++) {
ItemStack originalItem = items.get(slot);
ItemStack updatedItem = applyLore(
player,
wrapper.getWindowId(),
slot,
originalItem
);
modifiedItems.add(updatedItem == null ? originalItem : updatedItem);
}
wrapper.setItems(modifiedItems);

Optional<ItemStack> carriedItem = wrapper.getCarriedItem();
if (carriedItem.isPresent()) {
ItemStack updatedCarriedItem = applyLore(
player,
wrapper.getWindowId(),
CARRIED_ITEM_SLOT,
carriedItem.get()
);
if (updatedCarriedItem != null) {
wrapper.setCarriedItem(updatedCarriedItem);
}
}
}

private ItemStack applyLore(
Player player,
int windowId,
int slot,
ItemStack packetItem
) {
if (packetItem == null) return null;

org.bukkit.inventory.ItemStack bukkitItem = SpigotConversionUtil.toBukkitItemStack(packetItem);
if (bukkitItem == null || bukkitItem.getType().isAir()) return null;

ClientItemLoreEvent event = new ClientItemLoreEvent(player, windowId, slot, bukkitItem);
plugin.getServer().getPluginManager().callEvent(event);

if (!event.isModified()) {
return null;
}

org.bukkit.inventory.ItemStack clientItem = bukkitItem.clone();
ItemMeta meta = clientItem.getItemMeta();
if (meta == null) {
log.warn("Unable to apply client-side lore to item without item meta: {}", clientItem.getType());
return null;
}

meta.lore(event.getLore());
clientItem.setItemMeta(meta);
return SpigotConversionUtil.fromBukkitItemStack(clientItem);
}
}