Add cosmetic equipper implemented using protocollib, currently doesn't refresh equipment manually

This commit is contained in:
fullwall 2024-10-27 21:27:27 +08:00
parent 4cf042eb3f
commit 8111ba387f
3 changed files with 321 additions and 119 deletions

View File

@ -12,6 +12,7 @@ import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Play.Server; 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.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException; 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.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataValue; 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.NPCEvent;
import net.citizensnpcs.api.event.NPCSpawnEvent; import net.citizensnpcs.api.event.NPCSpawnEvent;
import net.citizensnpcs.api.npc.NPC; 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.trait.trait.MobType;
import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
@ -60,6 +68,57 @@ public class ProtocolLibListener implements Listener {
this.plugin = plugin; this.plugin = plugin;
manager = ProtocolLibrary.getProtocolManager(); manager = ProtocolLibrary.getProtocolManager();
Bukkit.getPluginManager().registerEvents(this, plugin); 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<List<Pair<EnumWrappers.ItemSlot, ItemStack>>> modifier = packet
.getLists(BukkitConverters.getPairConverter(EnumWrappers.getItemSlotConverter(),
BukkitConverters.getItemStackConverter()));
for (int i = 0; i < modifier.getValues().size(); i++) {
List<Pair<EnumWrappers.ItemSlot, ItemStack>> pairs = modifier.read(i);
boolean modified = false;
for (Pair<EnumWrappers.ItemSlot, ItemStack> 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) { manager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, Server.ENTITY_METADATA) {
@Override @Override
public void onPacketSending(PacketEvent event) { public void onPacketSending(PacketEvent event) {

View File

@ -8,10 +8,8 @@ import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import net.citizensnpcs.api.gui.CitizensInventoryClickEvent; import net.citizensnpcs.api.gui.CitizensInventoryClickEvent;
import net.citizensnpcs.api.gui.ClickHandler;
import net.citizensnpcs.api.gui.InjectContext; import net.citizensnpcs.api.gui.InjectContext;
import net.citizensnpcs.api.gui.InventoryMenuPage; import net.citizensnpcs.api.gui.InventoryMenuPage;
import net.citizensnpcs.api.gui.InventoryMenuSlot;
import net.citizensnpcs.api.gui.Menu; import net.citizensnpcs.api.gui.Menu;
import net.citizensnpcs.api.gui.MenuContext; import net.citizensnpcs.api.gui.MenuContext;
import net.citizensnpcs.api.gui.MenuPattern; 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;
import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot; 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( @MenuSlot(
slot = { 0, 1 }, slot = { 0, 1 },
compatMaterial = { "SHIELD", "BARRIER", "FIRE" }, 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, 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, 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, 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( @MenuPattern(
offset = { 0, 6 }, offset = { 0, 7 },
slots = { @MenuSlot(pat = 'x', compatMaterial = { "BARRIER", "FIRE" }, title = "<4>Unused") }, slots = { @MenuSlot(pat = 'x', compatMaterial = { "BARRIER", "FIRE" }, title = "<4>Unused") },
value = "xxx\nxxx") value = "xx\nxx\nxx")
public class GenericEquipperGUI extends InventoryMenuPage { 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 @InjectContext
private NPC npc; private NPC npc;
@MenuSlot(slot = { 1, 1 })
private InventoryMenuSlot offhand;
@Override @Override
public void initialise(MenuContext ctx) { public void initialise(MenuContext ctx) {
Equipment trait = npc.getOrAddTrait(Equipment.class); Equipment trait = npc.getOrAddTrait(Equipment.class);
hand.setItemStack(trait.get(EquipmentSlot.HAND)); EquipmentSlot[] slots = new EquipmentSlot[] { EquipmentSlot.HAND, EquipmentSlot.OFF_HAND, EquipmentSlot.HELMET,
helmet.setItemStack(trait.get(EquipmentSlot.HELMET)); EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS, EquipmentSlot.BOOTS, EquipmentSlot.BODY };
chest.setItemStack(trait.get(EquipmentSlot.CHESTPLATE)); for (int i = 0; i < slots.length; i++) {
leggings.setItemStack(trait.get(EquipmentSlot.LEGGINGS)); EquipmentSlot slot = slots[i];
boots.setItemStack(trait.get(EquipmentSlot.BOOTS)); ctx.getSlot(1 * 9 + i).setItemStack(trait.get(slot));
offhand.setItemStack(trait.get(EquipmentSlot.OFF_HAND)); if (trait.getCosmetic(slot) != null) {
ctx.getSlot(2 * 9 + i).setItemStack(trait.getCosmetic(slot));
}
Function<Material, Boolean> 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<Material, Boolean> 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<Material, Boolean> filter) { private void set(EquipmentSlot slot, CitizensInventoryClickEvent event, Function<Material, Boolean> filter) {
ItemStack result = event.getResultItemNonNull(); 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); event.setResult(Result.DENY);
return; return;
} }
npc.getOrAddTrait(Equipment.class).set(slot, result); npc.getOrAddTrait(Equipment.class).set(slot, result);
} }
@ClickHandler(slot = { 1, 5 }) private void setCosmetic(EquipmentSlot slot, CitizensInventoryClickEvent event,
public void setBoots(InventoryMenuSlot slot, CitizensInventoryClickEvent event) { Function<Material, Boolean> filter) {
set(EquipmentSlot.BOOTS, event, type -> type == Material.AIR || type.name().endsWith("BOOTS")); 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);
@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);
} }
} }

286
pom.xml
View File

@ -1,79 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?> <!-- Citizens build file -->
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging> <parent>
<groupId>net.citizensnpcs</groupId> <groupId>net.citizensnpcs</groupId>
<artifactId>citizens-parent</artifactId> <artifactId>citizens-parent</artifactId>
<version>2.0.36-SNAPSHOT</version> <version>2.0.36-SNAPSHOT</version>
</parent>
<artifactId>citizens-main</artifactId>
<properties> <properties>
<BUILD_NUMBER>Unknown</BUILD_NUMBER> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<CITIZENS_VERSION>2.0.36</CITIZENS_VERSION> <craftbukkit.version>1.21.3-R0.1-SNAPSHOT</craftbukkit.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version> <placeholderapi.version>2.11.5</placeholderapi.version>
<maven-assembly-plugin.version>3.7.1</maven-assembly-plugin.version> <citizensapi.version>${project.version}</citizensapi.version>
<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version> <worldguard.version>7.1.0-SNAPSHOT</worldguard.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.4.1</maven-jar-plugin.version>
<maven-shade-plugin.version>3.5.2</maven-shade-plugin.version>
<special-source-plugin.version>2.0.3</special-source-plugin.version>
</properties> </properties>
<distributionManagement>
<repository>
<id>citizens-repo</id>
<url>https://maven.citizensnpcs.co/repo</url>
</repository>
</distributionManagement>
<repositories> <repositories>
<repository> <repository>
<id>citizens-repo</id> <id>spigot-repo</id>
<url>https://maven.citizensnpcs.co/repo</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>placeholderapi</id>
<url>
https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>https://maven.enginehub.org/repo/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<repository>
<id>AlessioDP</id>
<url>https://repo.alessiodp.com/releases/</url>
</repository> </repository>
</repositories> </repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${craftbukkit.version}</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.5.15</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.ethz.globis.phtree</groupId>
<artifactId>phtree</artifactId>
<version>2.8.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.joml</groupId>
<artifactId>joml</artifactId>
<version>1.10.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.citizensnpcs</groupId>
<artifactId>citizensapi</artifactId>
<version>${citizensapi.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>${placeholderapi.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldguard</groupId>
<artifactId>worldguard-bukkit</artifactId>
<version>${worldguard.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-minimessage</artifactId>
<version>4.17.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<url>http://www.citizensnpcs.co</url>
<ciManagement>
<system>jenkins</system>
<url>http://ci.citizensnpcs.co</url>
</ciManagement>
<scm>
<connection>scm:git:git://github.com/CitizensDev/Citizens2.git</connection>
<developerConnection>scm:git:git:@github.com:CitizensDev/Citizens2.git</developerConnection>
<url>https://github.com/CitizensDev/Citizens2/tree/master/</url>
</scm>
<build> <build>
<defaultGoal>clean package install</defaultGoal> <defaultGoal>clean package install javadoc:javadoc</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>plugin.yml</include>
<include>*.json</include>
<include>LICENSE</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>8</release>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>net.citizensnpcs:citizensapi</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
<!--
<relocations>
<relocation>
<pattern>it.unimi.dsi</pattern>
<shadedPattern>clib.fastutil</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori</pattern>
<shadedPattern>clib.net.kyori</shadedPattern>
</relocation>
<relocation>
<pattern>net.byteflux.libby</pattern>
<shadedPattern>clib.net.byteflux.libby</shadedPattern>
</relocation>
</relocations>
-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<configuration>
<source>11</source>
<author>false</author>
<failOnError>false</failOnError>
<doclint>none</doclint>
<links>
<link>https://hub.spigotmc.org/javadocs/spigot</link>
</links>
<includeDependencySources>true</includeDependencySources>
<dependencySourceIncludes>
<dependencySourceInclude>net.citizensnpcs:citizensapi</dependencySourceInclude>
</dependencySourceIncludes>
<excludePackageNames>net.citizensnpcs.commands</excludePackageNames>
</configuration>
</plugin>
</plugins>
</build> </build>
<profiles>
<profile>
<id>spigot-release</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>main</module>
<module>v1_19_R3</module>
<module>v1_20_R4</module>
<module>v1_21_R2</module>
<module>dist</module>
</modules>
</profile>
<profile>
<id>full</id>
<modules>
<module>main</module>
<module>v1_8_R3</module>
<module>v1_10_R1</module>
<module>v1_11_R1</module>
<module>v1_12_R1</module>
<module>v1_13_R2</module>
<module>v1_14_R1</module>
<module>v1_15_R1</module>
<module>v1_16_R3</module>
<module>v1_17_R1</module>
<module>v1_18_R2</module>
<module>v1_19_R3</module>
<module>v1_20_R4</module>
<module>v1_21_R2</module>
<module>dist</module>
</modules>
</profile>
<profile>
<id>dev</id>
<modules>
<module>main</module>
<module>v1_21_R2</module>
<module>dist</module>
</modules>
</profile>
</profiles>
</project> </project>