From 8111ba387f1ae2d8d3eb7a69f4e4c8fda9d03ada Mon Sep 17 00:00:00 2001 From: fullwall Date: Sun, 27 Oct 2024 21:27:27 +0800 Subject: [PATCH] Add cosmetic equipper implemented using protocollib, currently doesn't refresh equipment manually --- .../net/citizensnpcs/ProtocolLibListener.java | 59 ++++ .../editor/GenericEquipperGUI.java | 89 +++--- pom.xml | 292 ++++++++++++++---- 3 files changed, 321 insertions(+), 119 deletions(-) diff --git a/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java b/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java index c3ab685bd..888f7b166 100644 --- a/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java +++ b/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java @@ -12,6 +12,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Play.Server; @@ -23,6 +24,11 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.EnumWrappers.ItemSlot; +import com.comphenix.protocol.wrappers.Pair; import com.comphenix.protocol.wrappers.PlayerInfoData; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedDataValue; @@ -39,6 +45,8 @@ import net.citizensnpcs.api.event.NPCDespawnEvent; import net.citizensnpcs.api.event.NPCEvent; import net.citizensnpcs.api.event.NPCSpawnEvent; import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.api.trait.trait.Equipment; +import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot; import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.npc.ai.NPCHolder; @@ -60,6 +68,57 @@ public class ProtocolLibListener implements Listener { this.plugin = plugin; manager = ProtocolLibrary.getProtocolManager(); Bukkit.getPluginManager().registerEvents(this, plugin); + manager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, Server.ENTITY_EQUIPMENT) { + private EquipmentSlot convert(ItemSlot slot) { + if (slot.name().equals("BODY")) + return EquipmentSlot.BODY; + switch (slot) { + case CHEST: + return EquipmentSlot.CHESTPLATE; + case FEET: + return EquipmentSlot.BOOTS; + case HEAD: + return EquipmentSlot.HELMET; + case LEGS: + return EquipmentSlot.LEGGINGS; + case MAINHAND: + return EquipmentSlot.HAND; + case OFFHAND: + return EquipmentSlot.OFF_HAND; + default: + return null; + } + } + + @Override + public void onPacketSending(PacketEvent event) { + NPC npc = getNPCFromPacket(event); + if (npc == null || !npc.hasTrait(Equipment.class)) + return; + Equipment trait = npc.getOrAddTrait(Equipment.class); + PacketContainer packet = event.getPacket(); + StructureModifier>> modifier = packet + .getLists(BukkitConverters.getPairConverter(EnumWrappers.getItemSlotConverter(), + BukkitConverters.getItemStackConverter())); + for (int i = 0; i < modifier.getValues().size(); i++) { + List> pairs = modifier.read(i); + boolean modified = false; + for (Pair pair : pairs) { + EquipmentSlot converted = convert(pair.getFirst()); + if (converted == null) + continue; + ItemStack cosmetic = trait.getCosmetic(converted); + if (cosmetic != null) { + pair.setSecond(cosmetic); + modified = true; + } + } + if (modified) { + modifier.write(i, pairs); + } + } + } + }); manager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, Server.ENTITY_METADATA) { @Override public void onPacketSending(PacketEvent event) { diff --git a/main/src/main/java/net/citizensnpcs/editor/GenericEquipperGUI.java b/main/src/main/java/net/citizensnpcs/editor/GenericEquipperGUI.java index 8c87f4a4f..21618139d 100644 --- a/main/src/main/java/net/citizensnpcs/editor/GenericEquipperGUI.java +++ b/main/src/main/java/net/citizensnpcs/editor/GenericEquipperGUI.java @@ -8,10 +8,8 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; import net.citizensnpcs.api.gui.CitizensInventoryClickEvent; -import net.citizensnpcs.api.gui.ClickHandler; import net.citizensnpcs.api.gui.InjectContext; import net.citizensnpcs.api.gui.InventoryMenuPage; -import net.citizensnpcs.api.gui.InventoryMenuSlot; import net.citizensnpcs.api.gui.Menu; import net.citizensnpcs.api.gui.MenuContext; import net.citizensnpcs.api.gui.MenuPattern; @@ -20,7 +18,7 @@ import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.trait.trait.Equipment; import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot; -@Menu(title = "NPC Equipment", type = InventoryType.CHEST, dimensions = { 2, 5 }) +@Menu(title = "NPC Equipment", type = InventoryType.CHEST, dimensions = { 3, 9 }) @MenuSlot( slot = { 0, 1 }, compatMaterial = { "SHIELD", "BARRIER", "FIRE" }, @@ -31,74 +29,59 @@ import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot; @MenuSlot(slot = { 0, 3 }, material = Material.DIAMOND_CHESTPLATE, lore = "Place chestplate below", amount = 1) @MenuSlot(slot = { 0, 4 }, material = Material.DIAMOND_LEGGINGS, lore = "Place leggings below", amount = 1) @MenuSlot(slot = { 0, 5 }, material = Material.DIAMOND_BOOTS, lore = "Place boots below", amount = 1) +@MenuSlot(slot = { 0, 6 }, material = Material.DIAMOND_CHESTPLATE, lore = "Place body item below", amount = 1) @MenuPattern( - offset = { 0, 6 }, + offset = { 0, 7 }, slots = { @MenuSlot(pat = 'x', compatMaterial = { "BARRIER", "FIRE" }, title = "<4>Unused") }, - value = "xxx\nxxx") + value = "xx\nxx\nxx") public class GenericEquipperGUI extends InventoryMenuPage { - @MenuSlot(slot = { 1, 5 }) - private InventoryMenuSlot boots; - @MenuSlot(slot = { 1, 3 }) - private InventoryMenuSlot chest; - @MenuSlot(slot = { 1, 0 }) - private InventoryMenuSlot hand; - @MenuSlot(slot = { 1, 2 }) - private InventoryMenuSlot helmet; - @MenuSlot(slot = { 1, 4 }) - private InventoryMenuSlot leggings; @InjectContext private NPC npc; - @MenuSlot(slot = { 1, 1 }) - private InventoryMenuSlot offhand; @Override public void initialise(MenuContext ctx) { Equipment trait = npc.getOrAddTrait(Equipment.class); - hand.setItemStack(trait.get(EquipmentSlot.HAND)); - helmet.setItemStack(trait.get(EquipmentSlot.HELMET)); - chest.setItemStack(trait.get(EquipmentSlot.CHESTPLATE)); - leggings.setItemStack(trait.get(EquipmentSlot.LEGGINGS)); - boots.setItemStack(trait.get(EquipmentSlot.BOOTS)); - offhand.setItemStack(trait.get(EquipmentSlot.OFF_HAND)); + EquipmentSlot[] slots = new EquipmentSlot[] { EquipmentSlot.HAND, EquipmentSlot.OFF_HAND, EquipmentSlot.HELMET, + EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS, EquipmentSlot.BOOTS, EquipmentSlot.BODY }; + for (int i = 0; i < slots.length; i++) { + EquipmentSlot slot = slots[i]; + ctx.getSlot(1 * 9 + i).setItemStack(trait.get(slot)); + if (trait.getCosmetic(slot) != null) { + ctx.getSlot(2 * 9 + i).setItemStack(trait.getCosmetic(slot)); + } + Function filter = type -> true; + switch (slot) { + case BOOTS: + case LEGGINGS: + filter = type -> type.name().endsWith(slot.name()); + break; + case CHESTPLATE: + filter = type -> type == Material.ELYTRA || type.name().endsWith(slot.name()); + default: + break; + } + Function ffilter = filter; + ctx.getSlot(1 * 9 + i).addClickHandler(event -> set(slot, event, ffilter)); + ctx.getSlot(2 * 9 + i).addClickHandler(event -> setCosmetic(slot, event, ffilter)); + } } private void set(EquipmentSlot slot, CitizensInventoryClickEvent event, Function filter) { ItemStack result = event.getResultItemNonNull(); - if (event.isCancelled() || !filter.apply(result.getType())) { + if (event.isCancelled() || (result.getType() != Material.AIR && !filter.apply(result.getType()))) { event.setResult(Result.DENY); return; } npc.getOrAddTrait(Equipment.class).set(slot, result); } - @ClickHandler(slot = { 1, 5 }) - public void setBoots(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.BOOTS, event, type -> type == Material.AIR || type.name().endsWith("BOOTS")); - } - - @ClickHandler(slot = { 1, 3 }) - public void setChest(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.CHESTPLATE, event, - type -> type == Material.AIR || type.name().endsWith("CHESTPLATE") || type.name().equals("ELYTRA")); - } - - @ClickHandler(slot = { 1, 0 }) - public void setHand(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.HAND, event, type -> true); - } - - @ClickHandler(slot = { 1, 2 }) - public void setHelmet(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.HELMET, event, type -> true); - } - - @ClickHandler(slot = { 1, 4 }) - public void setLeggings(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.LEGGINGS, event, type -> type == Material.AIR || type.name().endsWith("LEGGINGS")); - } - - @ClickHandler(slot = { 1, 1 }) - public void setOffhand(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { - set(EquipmentSlot.OFF_HAND, event, type -> true); + private void setCosmetic(EquipmentSlot slot, CitizensInventoryClickEvent event, + Function filter) { + ItemStack result = event.getResultItemNonNull(); + if (event.isCancelled() || (result.getType() != Material.AIR && !filter.apply(result.getType()))) { + event.setResult(Result.DENY); + return; + } + npc.getOrAddTrait(Equipment.class).setCosmetic(slot, result); } } diff --git a/pom.xml b/pom.xml index ccb78ca68..1acf48968 100644 --- a/pom.xml +++ b/pom.xml @@ -1,79 +1,239 @@ - + 4.0.0 - pom - net.citizensnpcs - citizens-parent - 2.0.36-SNAPSHOT + + net.citizensnpcs + citizens-parent + 2.0.36-SNAPSHOT + + citizens-main - Unknown - 2.0.36 - 3.6.3 - 3.7.1 - 3.1.1 - 3.13.0 - 3.4.1 - 3.5.2 - 2.0.3 + UTF-8 + 1.21.3-R0.1-SNAPSHOT + 2.11.5 + ${project.version} + 7.1.0-SNAPSHOT - - - citizens-repo - https://maven.citizensnpcs.co/repo - - - citizens-repo - https://maven.citizensnpcs.co/repo + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jitpack.io + https://jitpack.io + + false + + + + placeholderapi + + https://repo.extendedclip.com/content/repositories/placeholderapi/ + + + sk89q-repo + https://maven.enginehub.org/repo/ + + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + AlessioDP + https://repo.alessiodp.com/releases/ + + + org.spigotmc + spigot + ${craftbukkit.version} + jar + provided + + + it.unimi.dsi + fastutil + 8.5.15 + provided + + + com.comphenix.protocol + ProtocolLib + 5.3.0 + provided + + + ch.ethz.globis.phtree + phtree + 2.8.1 + provided + + + org.joml + joml + 1.10.8 + provided + + + net.citizensnpcs + citizensapi + ${citizensapi.version} + jar + compile + + + me.clip + placeholderapi + ${placeholderapi.version} + provided + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + com.sk89q.worldguard + worldguard-bukkit + ${worldguard.version} + provided + + + net.kyori + adventure-text-minimessage + 4.17.0 + provided + + + net.kyori + adventure-platform-bukkit + 4.3.3 + provided + + + org.reflections + reflections + 0.10.2 + test + + + http://www.citizensnpcs.co + + jenkins + http://ci.citizensnpcs.co + + + scm:git:git://github.com/CitizensDev/Citizens2.git + scm:git:git:@github.com:CitizensDev/Citizens2.git + https://github.com/CitizensDev/Citizens2/tree/master/ + - clean package install + clean package install javadoc:javadoc + src/main/java + + + . + true + src/main/resources + + plugin.yml + *.json + LICENSE + + + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + default-deploy + deploy + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + package + + shade + + + true + false + + + net.citizensnpcs:citizensapi + + ** + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + 11 + false + false + none + + https://hub.spigotmc.org/javadocs/spigot + + true + + net.citizensnpcs:citizensapi + + net.citizensnpcs.commands + + + - - - spigot-release - - true - - - main - v1_19_R3 - v1_20_R4 - v1_21_R2 - dist - - - - full - - main - v1_8_R3 - v1_10_R1 - v1_11_R1 - v1_12_R1 - v1_13_R2 - v1_14_R1 - v1_15_R1 - v1_16_R3 - v1_17_R1 - v1_18_R2 - v1_19_R3 - v1_20_R4 - v1_21_R2 - dist - - - - dev - - main - v1_21_R2 - dist - - - \ No newline at end of file