Merge remote-tracking branch 'upstream/2.X' into 2.X
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is failing

This commit is contained in:
bridge 2024-06-13 00:24:04 +02:00
commit e61ff12b4f
30 changed files with 514 additions and 361 deletions

@ -43,6 +43,14 @@ public interface PropertyHolder {
*/ */
void setItemProperty(EntityProperty<?> key, ItemStack value); void setItemProperty(EntityProperty<?> key, ItemStack value);
/**
* Weird fix which is sadly required in order to not decrease performance
* when using item properties, read https://github.com/Pyrbu/ZNPCsPlus/pull/129#issuecomment-1948777764
*
* @param key Unique key representing a property
*/
ItemStack getItemProperty(EntityProperty<?> key);
/** /**
* Method used to get a set of all of the property keys that this holder has a value for * Method used to get a set of all of the property keys that this holder has a value for
* *

@ -41,4 +41,16 @@ public interface Hologram {
* @return The number of lines in the hologram * @return The number of lines in the hologram
*/ */
int lineCount(); int lineCount();
/**
* Gets the refresh delay of the hologram
* @return The refresh delay of the hologram
*/
long getRefreshDelay();
/**
* Sets the refresh delay of the hologram
* @param delay The delay to set
*/
void setRefreshDelay(long delay);
} }

@ -0,0 +1,8 @@
package lol.pyr.znpcsplus.util;
public enum ArmadilloState {
IDLE,
ROLLING,
SCARED,
UNROLLING
}

@ -0,0 +1,23 @@
package lol.pyr.znpcsplus.util;
public enum WoldVariant {
PALE(3),
SPOTTED(6),
SNOWY(5),
BLACK(1),
ASHEN(0),
RUSTY(4),
WOODS(8),
CHESTNUT(2),
STRIPED(7);
private final int id;
WoldVariant(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

@ -6,9 +6,9 @@ plugins {
runServer { runServer {
javaLauncher = javaToolchains.launcherFor { javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17) languageVersion = JavaLanguageVersion.of(21)
} }
minecraftVersion "1.20.4" minecraftVersion "1.20.6"
} }
processResources { processResources {
@ -32,18 +32,17 @@ publishing {
dependencies { dependencies {
compileOnly "me.clip:placeholderapi:2.11.5" // Placeholder support compileOnly "me.clip:placeholderapi:2.11.5" // Placeholder support
compileOnly "com.google.code.gson:gson:2.10.1" // JSON parsing implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
compileOnly "org.bstats:bstats-bukkit:3.0.2" // Plugin stats implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
compileOnly "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker implementation "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker
compileOnly "com.github.retrooper.packetevents:spigot:2.3.0" // Packets implementation "com.github.retrooper.packetevents:spigot:2.3.0" // Packets
compileOnly "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
compileOnly "lol.pyr:director-adventure:2.1.1" // Commands implementation "lol.pyr:director-adventure:2.1.1" // Commands
// Fancy text library // Fancy text library
compileOnly "net.kyori:adventure-platform-bukkit:4.3.2" implementation "net.kyori:adventure-platform-bukkit:4.3.2"
compileOnly "net.kyori:adventure-text-minimessage:4.15.0" implementation "net.kyori:adventure-text-minimessage:4.15.0"
implementation "me.lucko:jar-relocator:1.7"
implementation project(":api") implementation project(":api")
} }

@ -115,7 +115,7 @@ public class ZNpcsPlus {
MojangSkinCache skinCache = new MojangSkinCache(configManager); MojangSkinCache skinCache = new MojangSkinCache(configManager);
EntityPropertyRegistryImpl propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager); EntityPropertyRegistryImpl propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager); PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
propertyRegistry.registerTypes(packetFactory, textSerializer); propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer);
ActionRegistry actionRegistry = new ActionRegistry(); ActionRegistry actionRegistry = new ActionRegistry();
NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl(); NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl();
@ -131,7 +131,7 @@ public class ZNpcsPlus {
scheduler, packetFactory, textSerializer, typeRegistry, getDataFolder().getParentFile(), scheduler, packetFactory, textSerializer, typeRegistry, getDataFolder().getParentFile(),
propertyRegistry, skinCache, npcRegistry, bungeeConnector); propertyRegistry, skinCache, npcRegistry, bungeeConnector);
log(ChatColor.WHITE + " * Registerring components..."); log(ChatColor.WHITE + " * Registering components...");
bungeeConnector.registerChannel(); bungeeConnector.registerChannel();
shutdownTasks.add(bungeeConnector::unregisterChannel); shutdownTasks.add(bungeeConnector::unregisterChannel);
@ -276,10 +276,13 @@ public class ZNpcsPlus {
registerEnumParser(manager, RabbitType.class, incorrectUsageMessage); registerEnumParser(manager, RabbitType.class, incorrectUsageMessage);
registerEnumParser(manager, AttachDirection.class, incorrectUsageMessage); registerEnumParser(manager, AttachDirection.class, incorrectUsageMessage);
registerEnumParser(manager, Sound.class, incorrectUsageMessage); registerEnumParser(manager, Sound.class, incorrectUsageMessage);
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root")) manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry)) .addSubcommand("center", new CenterCommand(npcRegistry))
.addSubcommand("create", new CreateCommand(npcRegistry, typeRegistry)) .addSubcommand("create", new CreateCommand(npcRegistry, typeRegistry))
.addSubcommand("clone", new CloneCommand(npcRegistry))
.addSubcommand("reloadconfig", new ReloadConfigCommand(configManager)) .addSubcommand("reloadconfig", new ReloadConfigCommand(configManager))
.addSubcommand("toggle", new ToggleCommand(npcRegistry)) .addSubcommand("toggle", new ToggleCommand(npcRegistry))
.addSubcommand("skin", new SkinCommand(skinCache, npcRegistry, typeRegistry, propertyRegistry)) .addSubcommand("skin", new SkinCommand(skinCache, npcRegistry, typeRegistry, propertyRegistry))

@ -2,7 +2,6 @@ package lol.pyr.znpcsplus;
import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.common.message.Message; import lol.pyr.director.common.message.Message;
import lol.pyr.znpcsplus.libraries.LibraryLoader;
import lol.pyr.znpcsplus.util.FileUtil; import lol.pyr.znpcsplus.util.FileUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
@ -32,55 +31,6 @@ public class ZNpcsPlusBootstrap extends JavaPlugin {
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
getLogger().info("Downloading and loading libraries, this might take a while if this is the first time you're launching the plugin");
LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries"));
loader.addRelocation(decrypt("org..bstats"), "lol.pyr.znpcsplus.libraries.bstats");
loader.addRelocation(decrypt("me..robertlit..spigotresources"), "lol.pyr.znpcsplus.libraries.spigotresources");
loader.addRelocation(decrypt("net..kyori"), "lol.pyr.znpcsplus.libraries.kyori");
loader.addRelocation(decrypt("org..checkerframework"), "lol.pyr.znpcsplus.libraries.checkerframework");
loader.addRelocation(decrypt("com..google"), "lol.pyr.znpcsplus.libraries.google");
loader.addRelocation(decrypt("com..github..retrooper..packetevents"), "lol.pyr.znpcsplus.libraries.packetevents.api");
loader.addRelocation(decrypt("io..github..retrooper..packetevents"), "lol.pyr.znpcsplus.libraries.packetevents.impl");
loader.addRelocation(decrypt("org..yaml..snakeyaml"), "lol.pyr.znpcsplus.libraries.snakeyaml");
loader.addRelocation(decrypt("space..arim..dazzleconf"), "lol.pyr.znpcsplus.libraries.dazzleconf");
loader.addRelocation(decrypt("lol..pyr..director"), "lol.pyr.znpcsplus.libraries.command");
loader.loadLibrary(decrypt("com..google..guava"), "guava", "18.0");
loader.loadLibrary(decrypt("com..google..code..gson"), "gson", "2.10.1");
loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2");
loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2");
loader.loadLibrary("me.robertlit", "SpigotResourcesAPI", "2.0", "https://repo.pyr.lol/releases");
loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "api", "2.3.0", "https://repo.codemc.io/repository/maven-releases/");
loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "spigot", "2.3.0", "https://repo.codemc.io/repository/maven-releases/");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1");
loader.loadLibrary("org.yaml", "snakeyaml", "1.33");
loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases");
loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson-legacy-impl", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json-legacy-impl", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0");
loader.deleteUnloadedLibraries();
getLogger().info("Loaded " + loader.loadedLibraryCount() + " libraries!");
zNpcsPlus = new ZNpcsPlus(this); zNpcsPlus = new ZNpcsPlus(this);
} }
@ -120,9 +70,4 @@ public class ZNpcsPlusBootstrap extends JavaPlugin {
public boolean movedLegacy() { public boolean movedLegacy() {
return legacy; return legacy;
} }
// Ugly hack because of https://github.com/johnrengelman/shadow/issues/232
private static String decrypt(String packageName) {
return packageName.replace("..", ".");
}
} }

@ -0,0 +1,44 @@
package lol.pyr.znpcsplus.commands;
import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
public class CloneCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
public CloneCommand(NpcRegistryImpl npcRegistry) {
this.npcRegistry = npcRegistry;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " clone <id> <new id>");
Player player = context.ensureSenderIsPlayer();
String id = context.popString();
if (npcRegistry.getById(id) == null) context.halt(Component.text("NPC with ID " + id + " does not exist.", NamedTextColor.RED));
String newId = context.popString();
if (npcRegistry.getById(newId) != null) context.halt(Component.text("NPC with ID " + newId + " already exists.", NamedTextColor.RED));
npcRegistry.clone(id, newId, player.getWorld(), new NpcLocation(player.getLocation()));
context.send(Component.text("Cloned NPC with ID " + id + " to ID " + newId + ".", NamedTextColor.GREEN));
}
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
return Collections.emptyList();
}
}

@ -13,6 +13,7 @@ import org.bukkit.entity.Player;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
public class MoveCommand implements CommandHandler { public class MoveCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry; private final NpcRegistryImpl npcRegistry;
@ -27,7 +28,7 @@ public class MoveCommand implements CommandHandler {
Player player = context.ensureSenderIsPlayer(); Player player = context.ensureSenderIsPlayer();
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
npc.setLocation(new NpcLocation(player.getLocation())); npc.setLocation(new NpcLocation(player.getLocation()));
if (!npc.getWorld().equals(player.getWorld())) npc.setWorld(player.getWorld()); if (!Objects.equals(npc.getWorld(), player.getWorld())) npc.setWorld(player.getWorld());
context.send(Component.text("NPC moved to your current location.", NamedTextColor.GREEN)); context.send(Component.text("NPC moved to your current location.", NamedTextColor.GREEN));
} }

@ -10,9 +10,11 @@ import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class NearCommand implements CommandHandler { public class NearCommand implements CommandHandler {
@ -30,8 +32,11 @@ public class NearCommand implements CommandHandler {
double radius = Math.pow(raw, 2); double radius = Math.pow(raw, 2);
List<NpcEntryImpl> entries = npcRegistry.getAllModifiable().stream() List<NpcEntryImpl> entries = npcRegistry.getAllModifiable().stream()
.filter(entry -> entry.getNpc().getWorld().equals(player.getWorld())) .filter(entry -> Objects.equals(entry.getNpc().getWorld(), player.getWorld()))
.filter(entry -> entry.getNpc().getBukkitLocation().distanceSquared(player.getLocation()) < radius) .filter(entry -> {
Location loc = entry.getNpc().getBukkitLocation();
return loc != null && loc.distanceSquared(player.getLocation()) < radius;
})
.collect(Collectors.toList()); .collect(Collectors.toList());
if (entries.isEmpty()) context.halt(Component.text("There are no npcs within " + raw + " blocks around you.", NamedTextColor.RED)); if (entries.isEmpty()) context.halt(Component.text("There are no npcs within " + raw + " blocks around you.", NamedTextColor.RED));

@ -9,6 +9,7 @@ import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FoliaUtil; import lol.pyr.znpcsplus.util.FoliaUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.Collections; import java.util.Collections;
@ -26,7 +27,9 @@ public class TeleportCommand implements CommandHandler {
context.setUsage(context.getLabel() + " teleport <id>"); context.setUsage(context.getLabel() + " teleport <id>");
Player player = context.ensureSenderIsPlayer(); Player player = context.ensureSenderIsPlayer();
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
FoliaUtil.teleport(player, npc.getBukkitLocation()); Location location = npc.getBukkitLocation();
if (location == null) context.halt("Unable to teleport to NPC, the world is not loaded!");
FoliaUtil.teleport(player, location);
context.send(Component.text("Teleported to NPC!", NamedTextColor.GREEN)); context.send(Component.text("Teleported to NPC!", NamedTextColor.GREEN));
} }

@ -4,6 +4,7 @@ import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.DataImporter; import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTrait; import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTrait;
import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTraitsRegistry; import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTraitsRegistry;
import lol.pyr.znpcsplus.conversion.citizens.model.traits.TypeTrait;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcImpl;
@ -52,7 +53,7 @@ public class CitizensImporter implements DataImporter {
this.propertyRegistry = propertyRegistry; this.propertyRegistry = propertyRegistry;
this.skinCache = skinCache; this.skinCache = skinCache;
this.dataFile = dataFile; this.dataFile = dataFile;
this.traitsRegistry = new CitizensTraitsRegistry(typeRegistry, propertyRegistry, skinCache); this.traitsRegistry = new CitizensTraitsRegistry(propertyRegistry, skinCache, taskScheduler, textSerializer);
this.npcRegistry = npcRegistry; this.npcRegistry = npcRegistry;
} }
@ -81,11 +82,12 @@ public class CitizensImporter implements DataImporter {
world = Bukkit.getWorlds().get(0).getName(); world = Bukkit.getWorlds().get(0).getName();
} }
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world, typeRegistry.getByName("armor_stand"), new NpcLocation(0, 0, 0, 0, 0)); NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world, typeRegistry.getByName("armor_stand"), new NpcLocation(0, 0, 0, 0, 0));
npc.getType().applyDefaultProperties(npc);
npc.getHologram().addTextLineComponent(textSerializer.deserialize(name));
ConfigurationSection traits = npcSection.getConfigurationSection("traits"); ConfigurationSection traits = npcSection.getConfigurationSection("traits");
if (traits != null) { if (traits != null) {
TypeTrait typeTrait = new TypeTrait(typeRegistry);
npc = typeTrait.apply(npc, traits.getString("type"));
npc.getType().applyDefaultProperties(npc);
for (String traitName : traits.getKeys(false)) { for (String traitName : traits.getKeys(false)) {
Object trait = traits.get(traitName); Object trait = traits.get(traitName);
CitizensTrait citizensTrait = traitsRegistry.getByName(traitName); CitizensTrait citizensTrait = traitsRegistry.getByName(traitName);
@ -94,6 +96,10 @@ public class CitizensImporter implements DataImporter {
} }
} }
} }
boolean nameVisible = Boolean.parseBoolean(npcSection.getString("metadata.name-visible", "true"));
if (nameVisible) {
npc.getHologram().addTextLineComponent(textSerializer.deserialize(name));
}
String id = key.toLowerCase(); String id = key.toLowerCase();
while (npcRegistry.getById(id) != null) { while (npcRegistry.getById(id) != null) {
id += "_"; // TODO: make a backup of the old npc instead id += "_"; // TODO: make a backup of the old npc instead

@ -1,24 +1,28 @@
package lol.pyr.znpcsplus.conversion.citizens.model; package lol.pyr.znpcsplus.conversion.citizens.model;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry;
import lol.pyr.znpcsplus.conversion.citizens.model.traits.*; import lol.pyr.znpcsplus.conversion.citizens.model.traits.*;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.HashMap; import java.util.HashMap;
public class CitizensTraitsRegistry { public class CitizensTraitsRegistry {
private final HashMap<String, CitizensTrait> traitMap = new HashMap<>(); private final HashMap<String, CitizensTrait> traitMap = new HashMap<>();
public CitizensTraitsRegistry(NpcTypeRegistry typeRegistry, EntityPropertyRegistry propertyRegistry, MojangSkinCache skinCache) { public CitizensTraitsRegistry(EntityPropertyRegistry propertyRegistry, MojangSkinCache skinCache, TaskScheduler taskScheduler, LegacyComponentSerializer textSerializer) {
register(new LocationTrait()); register(new LocationTrait());
register(new TypeTrait(typeRegistry));
register(new ProfessionTrait(propertyRegistry)); register(new ProfessionTrait(propertyRegistry));
register(new VillagerTrait(propertyRegistry)); register(new VillagerTrait(propertyRegistry));
register(new SkinTrait(propertyRegistry)); register(new SkinTrait(propertyRegistry));
register(new MirrorTrait(propertyRegistry, skinCache)); register(new MirrorTrait(propertyRegistry, skinCache));
register(new SkinLayersTrait(propertyRegistry)); register(new SkinLayersTrait(propertyRegistry));
register(new LookTrait(propertyRegistry)); register(new LookTrait(propertyRegistry));
register(new CommandTrait(taskScheduler));
register(new HologramTrait(textSerializer));
register(new EquipmentTrait(propertyRegistry));
register(new SpawnedTrait());
} }
public CitizensTrait getByName(String name) { public CitizensTrait getByName(String name) {

@ -0,0 +1,69 @@
package lol.pyr.znpcsplus.conversion.citizens.model.traits;
import lol.pyr.znpcsplus.api.interaction.InteractionType;
import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait;
import lol.pyr.znpcsplus.interaction.InteractionActionImpl;
import lol.pyr.znpcsplus.interaction.consolecommand.ConsoleCommandAction;
import lol.pyr.znpcsplus.interaction.playercommand.PlayerCommandAction;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
public class CommandTrait extends SectionCitizensTrait {
private final TaskScheduler scheduler;
public CommandTrait(TaskScheduler scheduler) {
super("commandtrait");
this.scheduler = scheduler;
}
@Override
public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) {
ConfigurationSection commands = section.getConfigurationSection("commands");
if (commands != null) {
Set<String> keys = commands.getKeys(false);
if (keys != null) {
for (String key : keys) {
ConfigurationSection commandSection = commands.getConfigurationSection(key);
String command = commandSection.getString("command");
String hand = commandSection.getString("hand", "BOTH");
InteractionType clickType = wrapClickType(hand);
boolean isPlayerCommand = commandSection.getBoolean("player", true);
int cooldown = commandSection.getInt("cooldown", 0);
int delay = commandSection.getInt("delay", 0);
if (command != null) {
InteractionActionImpl action;
if (isPlayerCommand) {
action = new PlayerCommandAction(scheduler, command, clickType, cooldown, delay);
} else {
action = new ConsoleCommandAction(scheduler, command, clickType, cooldown, delay);
}
npc.addAction(action);
}
}
}
}
return npc;
}
private InteractionType wrapClickType(String hand) {
if (hand == null) {
return InteractionType.ANY_CLICK;
}
switch (hand) {
case "RIGHT":
case "SHIFT_RIGHT":
return InteractionType.RIGHT_CLICK;
case "LEFT":
case "SHIFT_LEFT":
return InteractionType.LEFT_CLICK;
case "BOTH":
return InteractionType.ANY_CLICK;
}
throw new IllegalStateException();
}
}

@ -0,0 +1,115 @@
package lol.pyr.znpcsplus.conversion.citizens.model.traits;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
import com.google.common.io.BaseEncoding;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait;
import lol.pyr.znpcsplus.npc.NpcImpl;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class EquipmentTrait extends SectionCitizensTrait {
private final EntityPropertyRegistry propertyRegistry;
private final HashMap<String, EquipmentSlot> EQUIPMENT_SLOT_MAP = new HashMap<>();
public EquipmentTrait(EntityPropertyRegistry propertyRegistry) {
super("equipment");
this.propertyRegistry = propertyRegistry;
EQUIPMENT_SLOT_MAP.put("hand", EquipmentSlot.MAIN_HAND);
EQUIPMENT_SLOT_MAP.put("offhand", EquipmentSlot.OFF_HAND);
EQUIPMENT_SLOT_MAP.put("helmet", EquipmentSlot.HELMET);
EQUIPMENT_SLOT_MAP.put("chestplate", EquipmentSlot.CHEST_PLATE);
EQUIPMENT_SLOT_MAP.put("leggings", EquipmentSlot.LEGGINGS);
EQUIPMENT_SLOT_MAP.put("boots", EquipmentSlot.BOOTS);
}
@Override
public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) {
for (String key : section.getKeys(false)) {
EquipmentSlot slot = EQUIPMENT_SLOT_MAP.get(key);
if (slot == null) {
continue;
}
ItemStack itemStack = parseItemStack(section.getConfigurationSection(key));
if (itemStack == null) {
continue;
}
EntityProperty<ItemStack> property = propertyRegistry.getByName(key, ItemStack.class);
npc.setProperty(property, itemStack);
}
return npc;
}
private ItemStack parseItemStack(ConfigurationSection section) {
Material material = null;
if (section.isString("type_key")) {
material = Material.getMaterial(section.getString("type_key").toUpperCase());
} else if (section.isString("type")) {
material = Material.matchMaterial(section.getString("type").toUpperCase());
} else if (section.isString("id")) {
material = Material.matchMaterial(section.getString("id").toUpperCase());
}
if (material == null || material == Material.AIR) {
return null;
}
org.bukkit.inventory.ItemStack itemStack = new org.bukkit.inventory.ItemStack(material, section.getInt("amount", 1),
(short) section.getInt("durability", section.getInt("data", 0)));
if (section.isInt("mdata")) {
//noinspection deprecation
itemStack.getData().setData((byte) section.getInt("mdata"));
}
if (section.isConfigurationSection("enchantments")) {
ConfigurationSection enchantments = section.getConfigurationSection("enchantments");
itemStack.addUnsafeEnchantments(deserializeEnchantments(enchantments));
}
if (section.isConfigurationSection("meta")) {
ItemMeta itemMeta = deserializeMeta(section.getConfigurationSection("meta"));
if (itemMeta != null) {
itemStack.setItemMeta(itemMeta);
}
}
return SpigotConversionUtil.fromBukkitItemStack(itemStack);
}
private Map<Enchantment, Integer> deserializeEnchantments(ConfigurationSection section) {
Map<Enchantment, Integer> enchantments = new HashMap<>();
for (String key : section.getKeys(false)) {
Enchantment enchantment = Enchantment.getByName(key);
if (enchantment == null) {
continue;
}
enchantments.put(enchantment, section.getInt(key));
}
return enchantments;
}
private ItemMeta deserializeMeta(ConfigurationSection section) {
if (section.isString("encoded-meta")) {
byte[] raw = BaseEncoding.base64().decode(section.getString("encoded-meta"));
try {
BukkitObjectInputStream inp = new BukkitObjectInputStream(new ByteArrayInputStream(raw));
ItemMeta meta = (ItemMeta) inp.readObject();
inp.close();
return meta;
} catch (IOException | ClassNotFoundException e1) {
e1.printStackTrace();
}
}
return null;
}
}

@ -0,0 +1,36 @@
package lol.pyr.znpcsplus.conversion.citizens.model.traits;
import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait;
import lol.pyr.znpcsplus.npc.NpcImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class HologramTrait extends SectionCitizensTrait {
private final LegacyComponentSerializer textSerializer;
public HologramTrait(LegacyComponentSerializer textSerializer) {
super("hologramtrait");
this.textSerializer = textSerializer;
}
@Override
public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) {
ConfigurationSection linesSection = section.getConfigurationSection("lines");
if (linesSection != null) {
List<String> keys = new ArrayList<>(linesSection.getKeys(false));
for (int i = keys.size() - 1; i >= 0; i--) {
String line = linesSection.getConfigurationSection(keys.get(i)).getString("text");
if (line != null) {
Component component = textSerializer.deserialize(line);
npc.getHologram().addTextLineComponent(component);
}
}
}
return npc;
}
}

@ -0,0 +1,20 @@
package lol.pyr.znpcsplus.conversion.citizens.model.traits;
import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTrait;
import lol.pyr.znpcsplus.npc.NpcImpl;
import org.jetbrains.annotations.NotNull;
public class SpawnedTrait extends CitizensTrait {
public SpawnedTrait() {
super("spawned");
}
@Override
public @NotNull NpcImpl apply(NpcImpl npc, Object value) {
if (value != null) {
npc.setEnabled((boolean) value);
}
return npc;
}
}

@ -157,6 +157,7 @@ public class ZNpcImporter implements DataImporter {
HologramImpl hologram = npc.getHologram(); HologramImpl hologram = npc.getHologram();
hologram.setOffset(model.getHologramHeight()); hologram.setOffset(model.getHologramHeight());
Collections.reverse(model.getHologramLines());
for (String raw : model.getHologramLines()) { for (String raw : model.getHologramLines()) {
Component line = textSerializer.deserialize(raw); Component line = textSerializer.deserialize(raw);
hologram.addTextLineComponent(line); hologram.addTextLineComponent(line);

@ -9,6 +9,7 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTString; import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot; import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
import com.github.retrooper.packetevents.protocol.world.BlockFace; import com.github.retrooper.packetevents.protocol.world.BlockFace;
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
@ -86,6 +87,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerEnumSerializer(RabbitType.class); registerEnumSerializer(RabbitType.class);
registerEnumSerializer(AttachDirection.class); registerEnumSerializer(AttachDirection.class);
registerEnumSerializer(Sound.class); registerEnumSerializer(Sound.class);
registerEnumSerializer(ArmadilloState.class);
registerEnumSerializer(WoldVariant.class);
registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class); registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
@ -101,7 +104,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
*/ */
} }
public void registerTypes(PacketFactory packetFactory, LegacyComponentSerializer textSerializer) { public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer) {
ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion(); ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9); boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9); boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
@ -124,6 +127,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new DummyProperty<>("permission_required", false)); register(new DummyProperty<>("permission_required", false));
register(new ForceBodyRotationProperty(plugin));
register(new DummyProperty<>("player_knockback", false)); register(new DummyProperty<>("player_knockback", false));
register(new DummyProperty<>("player_knockback_exempt_permission", String.class)); register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
register(new DummyProperty<>("player_knockback_distance", 0.4)); register(new DummyProperty<>("player_knockback_distance", 0.4));
@ -411,7 +416,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new EncodedByteProperty<>("wolf_collar", DyeColor.BLUE, wolfIndex++, DyeColor::getDyeData)); register(new EncodedByteProperty<>("wolf_collar", DyeColor.BLUE, wolfIndex++, DyeColor::getDyeData));
} else register(new EncodedIntegerProperty<>("wolf_collar", DyeColor.RED, wolfIndex++, Enum::ordinal)); } else register(new EncodedIntegerProperty<>("wolf_collar", DyeColor.RED, wolfIndex++, Enum::ordinal));
if (ver.isNewerThanOrEquals(ServerVersion.V_1_16)) { if (ver.isNewerThanOrEquals(ServerVersion.V_1_16)) {
register(new EncodedIntegerProperty<>("wolf_angry", false, wolfIndex, b -> b ? 1 : 0)); register(new EncodedIntegerProperty<>("wolf_angry", false, wolfIndex++, b -> b ? 1 : 0));
linkProperties("tamed", "sitting"); linkProperties("tamed", "sitting");
} }
else { else {
@ -634,6 +639,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
// Frog // Frog
register(new EncodedIntegerProperty<>("frog_variant", FrogVariant.TEMPERATE, 17, Enum::ordinal, EntityDataTypes.FROG_VARIANT)); register(new EncodedIntegerProperty<>("frog_variant", FrogVariant.TEMPERATE, 17, Enum::ordinal, EntityDataTypes.FROG_VARIANT));
// Warden
register(new EncodedIntegerProperty<>("warden_anger", 0, 16, b -> Math.min(150, Math.max(0, b))));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20)) return; if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
// Camel // Camel
@ -643,6 +651,14 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
// Sniffer // Sniffer
register(new CustomTypeProperty<>("sniffer_state", 17, SnifferState.IDLING, EntityDataTypes.SNIFFER_STATE, state -> com.github.retrooper.packetevents.protocol.entity.sniffer.SnifferState.valueOf(state.name()))); register(new CustomTypeProperty<>("sniffer_state", 17, SnifferState.IDLING, EntityDataTypes.SNIFFER_STATE, state -> com.github.retrooper.packetevents.protocol.entity.sniffer.SnifferState.valueOf(state.name())));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
// Armadillo
register(new CustomTypeProperty<>("armadillo_state", 17, ArmadilloState.IDLE, EntityDataTypes.ARMADILLO_STATE, state ->
com.github.retrooper.packetevents.protocol.entity.armadillo.ArmadilloState.valueOf(state.name())));
// Wolf
register(new EncodedIntegerProperty<>("wolf_variant", WoldVariant.PALE, wolfIndex, WoldVariant::getId, EntityDataTypes.WOLF_VARIANT));
} }
private void registerSerializer(PropertySerializer<?> serializer) { private void registerSerializer(PropertySerializer<?> serializer) {

@ -107,6 +107,11 @@ public class PacketEntity implements PropertyHolder {
properties.setItemProperty(key, value); properties.setItemProperty(key, value);
} }
@Override
public ItemStack getItemProperty(EntityProperty<?> key) {
return properties.getItemProperty(key);
}
@Override @Override
public Set<EntityProperty<?>> getAppliedProperties() { public Set<EntityProperty<?>> getAppliedProperties() {
return properties.getAppliedProperties(); return properties.getAppliedProperties();

@ -0,0 +1,24 @@
package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
import lol.pyr.znpcsplus.entity.PacketEntity;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Map;
public class ForceBodyRotationProperty extends DummyProperty<Boolean> {
private final ZNpcsPlusBootstrap plugin;
public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin) {
super("force_body_rotation", false);
this.plugin = plugin;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 2L);
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 6L);
}
}

@ -147,10 +147,12 @@ public class HologramImpl extends Viewable implements Hologram {
for (HologramLine<?> line : lines) line.hide(player); for (HologramLine<?> line : lines) line.hide(player);
} }
@Override
public long getRefreshDelay() { public long getRefreshDelay() {
return refreshDelay; return refreshDelay;
} }
@Override
public void setRefreshDelay(long refreshDelay) { public void setRefreshDelay(long refreshDelay) {
this.refreshDelay = refreshDelay; this.refreshDelay = refreshDelay;
} }

@ -1,6 +1,7 @@
package lol.pyr.znpcsplus.hologram; package lol.pyr.znpcsplus.hologram;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
@ -76,6 +77,12 @@ public class HologramLine<M> implements PropertyHolder {
throw new UnsupportedOperationException("Can't set properties on a hologram line"); throw new UnsupportedOperationException("Can't set properties on a hologram line");
} }
@SuppressWarnings("unchecked")
@Override
public ItemStack getItemProperty(EntityProperty<?> key) {
return SpigotConversionUtil.toBukkitItemStack(((EntityProperty<com.github.retrooper.packetevents.protocol.item.ItemStack>) key).getDefaultValue());
}
@Override @Override
public Set<EntityProperty<?>> getAppliedProperties() { public Set<EntityProperty<?>> getAppliedProperties() {
return properties; return properties;

@ -1,119 +0,0 @@
package lol.pyr.znpcsplus.libraries;
import me.lucko.jarrelocator.JarRelocator;
import me.lucko.jarrelocator.Relocation;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
public class LibraryLoader {
private final static Logger logger = Logger.getLogger("ZNPCsPlus Library Loader");
private final UrlClassLoaderAccess loaderAccess;
private final File librariesFolder;
private final Set<File> loadedLibraries = new HashSet<>();
private final List<Relocation> relocationRules = new ArrayList<>();
public LibraryLoader(Plugin plugin, File librariesFolder) {
loaderAccess = UrlClassLoaderAccess.create((URLClassLoader) plugin.getClass().getClassLoader());
this.librariesFolder = librariesFolder;
if (!librariesFolder.exists()) librariesFolder.mkdirs();
}
public void deleteUnloadedLibraries() {
File[] files = librariesFolder.listFiles();
if (files == null) return;
for (File file : files) if (!loadedLibraries.contains(file)) file.delete();
}
public void addRelocation(String pre, String post) {
relocationRules.add(new Relocation(pre, post));
}
public void loadSnapshotLibrary(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) {
try {
loadLibrary(groupId + ":" + artifactId + ":" + version,
getDependencyFile(groupId, artifactId, version),
getSnapshotDependencyUrl(groupId, artifactId, version, snapshotVersion, repoUrl));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public int loadedLibraryCount() {
return loadedLibraries.size();
}
public void loadLibrary(String groupId, String artifactId, String version) {
loadLibrary(groupId, artifactId, version, "https://repo1.maven.org/maven2");
}
public void loadLibrary(String groupId, String artifactId, String version, String repoUrl) {
try {
loadLibrary(groupId + ":" + artifactId + ":" + version,
getDependencyFile(groupId, artifactId, version),
getDependencyUrl(groupId, artifactId, version, repoUrl));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private void loadLibrary(String name, File file, URL url) {
if (!file.exists()) {
try (InputStream in = url.openStream()) {
File temp = new File(file.getParentFile(), file.getName() + ".temp");
Files.copy(in, temp.toPath());
new JarRelocator(temp, file, relocationRules).run();
temp.delete();
// logger.info("Downloaded library " + name);
} catch (IOException e) {
logger.severe("Failed to download library " + name);
e.printStackTrace();
}
}
try {
loaderAccess.addURL(file.toURI().toURL());
loadedLibraries.add(file);
// logger.info("Loaded library " + name);
} catch (Exception e) {
logger.severe("Failed to load library, plugin may not work correctly (" + name + ")");
e.printStackTrace();
}
}
private File getDependencyFile(String groupId, String artifactId, String version) {
return new File(librariesFolder, groupId.replace(".", "-") + "-"
+ artifactId.replace(".", "-") + "-"
+ version.replace(".", "-") + ".jar");
}
private static URL getDependencyUrl(String groupId, String artifactId, String version, String repoUrl) throws MalformedURLException {
String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/";
url += groupId.replace(".", "/") + "/";
url += artifactId + "/";
url += version + "/";
url += artifactId + "-" + version + ".jar";
return new URL(url);
}
private static URL getSnapshotDependencyUrl(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) throws MalformedURLException {
String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/";
url += groupId.replace(".", "/") + "/";
url += artifactId + "/";
url += version + "/";
url += artifactId + "-" + snapshotVersion + ".jar";
return new URL(url);
}
}

@ -1,152 +0,0 @@
package lol.pyr.znpcsplus.libraries;
import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* Provides access to {@link URLClassLoader}#addURL.
* From https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/maven/URLClassLoaderAccess.java
*/
public abstract class UrlClassLoaderAccess {
/**
* Creates a {@link UrlClassLoaderAccess} for the given class loader.
*
* @param classLoader the class loader
* @return the access object
*/
static UrlClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) {
return new Reflection(classLoader);
} else if (Unsafe.isSupported()) {
return new Unsafe(classLoader);
} else {
return Noop.INSTANCE;
}
}
private final URLClassLoader classLoader;
protected UrlClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(@Nonnull URL url);
/**
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends UrlClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
}
private static boolean isSupported() {
return ADD_URL_METHOD != null;
}
Reflection(URLClassLoader classLoader) {
super(classLoader);
}
@Override
public void addURL(@Nonnull URL url) {
try {
ADD_URL_METHOD.invoke(super.classLoader, url);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
*
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
*/
private static class Unsafe extends UrlClassLoaderAccess {
private static final sun.misc.Unsafe UNSAFE;
static {
sun.misc.Unsafe unsafe;
try {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
} catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
private static boolean isSupported() {
return UNSAFE != null;
}
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(@Nonnull URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
private static class Noop extends UrlClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(@Nonnull URL url) {
throw new UnsupportedOperationException();
}
}
}

@ -20,6 +20,7 @@ import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -75,8 +76,10 @@ public class NpcImpl extends Viewable implements Npc {
return location; return location;
} }
public Location getBukkitLocation() { public @Nullable Location getBukkitLocation() {
return location.toBukkitLocation(getWorld()); World world = getWorld();
if (world == null) return null;
return location.toBukkitLocation(world);
} }
public void setLocation(NpcLocation location) { public void setLocation(NpcLocation location) {
@ -112,7 +115,7 @@ public class NpcImpl extends Viewable implements Npc {
return uuid; return uuid;
} }
public World getWorld() { public @Nullable World getWorld() {
return Bukkit.getWorld(worldName); return Bukkit.getWorld(worldName);
} }
@ -133,6 +136,7 @@ public class NpcImpl extends Viewable implements Npc {
} }
private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) { private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) {
if (!type.isAllowedProperty(property)) return;
for (Player viewer : getViewers()) { for (Player viewer : getViewers()) {
List<EntityData> data = property.applyStandalone(viewer, entity, true); List<EntityData> data = property.applyStandalone(viewer, entity, true);
if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data); if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data);
@ -164,6 +168,12 @@ public class NpcImpl extends Viewable implements Npc {
setProperty((EntityPropertyImpl<com.github.retrooper.packetevents.protocol.item.ItemStack>) key, SpigotConversionUtil.fromBukkitItemStack(value)); setProperty((EntityPropertyImpl<com.github.retrooper.packetevents.protocol.item.ItemStack>) key, SpigotConversionUtil.fromBukkitItemStack(value));
} }
@SuppressWarnings("unchecked")
@Override
public ItemStack getItemProperty(EntityProperty<?> key) {
return SpigotConversionUtil.toBukkitItemStack(getProperty((EntityProperty<com.github.retrooper.packetevents.protocol.item.ItemStack>) key));
}
public <T> void setProperty(EntityPropertyImpl<T> key, T value) { public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
if (key == null) return; if (key == null) return;
if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key); if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key);
@ -176,6 +186,11 @@ public class NpcImpl extends Viewable implements Npc {
setProperty((EntityPropertyImpl<T>) property, (T) value); setProperty((EntityPropertyImpl<T>) property, (T) value);
} }
@SuppressWarnings("unchecked")
public <T> void UNSAFE_setProperty(EntityProperty<?> property, Object value) {
setProperty((EntityPropertyImpl<T>) property, (T) value);
}
public Set<EntityProperty<?>> getAllProperties() { public Set<EntityProperty<?>> getAllProperties() {
return Collections.unmodifiableSet(propertyMap.keySet()); return Collections.unmodifiableSet(propertyMap.keySet());
} }

@ -1,12 +1,17 @@
package lol.pyr.znpcsplus.npc; package lol.pyr.znpcsplus.npc;
import lol.pyr.znpcsplus.ZNpcsPlus; import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.npc.NpcEntry; import lol.pyr.znpcsplus.api.npc.NpcEntry;
import lol.pyr.znpcsplus.api.npc.NpcRegistry; import lol.pyr.znpcsplus.api.npc.NpcRegistry;
import lol.pyr.znpcsplus.api.npc.NpcType; import lol.pyr.znpcsplus.api.npc.NpcType;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.hologram.HologramItem;
import lol.pyr.znpcsplus.hologram.HologramLine;
import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.interaction.ActionRegistry; import lol.pyr.znpcsplus.interaction.ActionRegistry;
import lol.pyr.znpcsplus.interaction.InteractionActionImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.storage.NpcStorage; import lol.pyr.znpcsplus.storage.NpcStorage;
@ -153,6 +158,35 @@ public class NpcRegistryImpl implements NpcRegistry {
return entry; return entry;
} }
public NpcEntryImpl clone(String id, String newId, World newWorld, NpcLocation newLocation) {
NpcEntryImpl oldNpc = getById(id);
if (oldNpc == null) return null;
NpcEntryImpl newNpc = create(newId, newWorld, oldNpc.getNpc().getType(), newLocation);
newNpc.enableEverything();
for (EntityProperty<?> property : oldNpc.getNpc().getAllProperties()) {
newNpc.getNpc().UNSAFE_setProperty(property, oldNpc.getNpc().getProperty(property));
}
for (InteractionActionImpl action : oldNpc.getNpc().getActions()) {
newNpc.getNpc().addAction(action);
}
for (HologramLine<?> line : oldNpc.getNpc().getHologram().getLines()) {
if (line instanceof HologramText) {
HologramText text = (HologramText) line;
newNpc.getNpc().getHologram().addTextLineComponent(text.getValue());
}
else if (line instanceof HologramItem) {
HologramItem item = (HologramItem) line;
newNpc.getNpc().getHologram().addItemLinePEStack(item.getValue());
}
else throw new IllegalArgumentException("Unknown hologram line type during clone");
}
return newNpc;
}
@Override @Override
public void delete(String id) { public void delete(String id) {
NpcEntryImpl entry = npcIdLookupMap.get(id.toLowerCase()); NpcEntryImpl entry = npcIdLookupMap.get(id.toLowerCase());

@ -164,12 +164,18 @@ public class NpcTypeImpl implements NpcType {
addProperties("panda_eating"); addProperties("panda_eating");
} }
} }
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_TAMEABLE_ANIMAL)) { if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_TAMEABLE_ANIMAL) &&
!(version.isNewerThanOrEquals(ServerVersion.V_1_14) && type.equals(EntityTypes.OCELOT))) {
addProperties("tamed", "sitting"); addProperties("tamed", "sitting");
} }
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.GUARDIAN)) { if (EntityTypes.isTypeInstanceOf(type, EntityTypes.GUARDIAN)) {
addProperties("is_retracting_spikes"); addProperties("is_retracting_spikes");
} }
if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) {
addProperties("wolf_variant");
}
}
return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties); return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties);
} }
} }

@ -36,7 +36,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "player", EntityTypes.PLAYER) register(builder(p, "player", EntityTypes.PLAYER)
.setHologramOffset(-0.15D) .setHologramOffset(-0.15D)
.addEquipmentProperties() .addEquipmentProperties()
.addProperties("skin_cape", "skin_jacket", "skin_left_sleeve", "skin_right_sleeve", "skin_left_leg", "skin_right_leg", "skin_hat", "shoulder_entity_left", "shoulder_entity_right") .addProperties("skin_cape", "skin_jacket", "skin_left_sleeve", "skin_right_sleeve", "skin_left_leg", "skin_right_leg", "skin_hat", "shoulder_entity_left", "shoulder_entity_right", "force_body_rotation")
.addDefaultProperty("skin_cape", true) .addDefaultProperty("skin_cape", true)
.addDefaultProperty("skin_jacket", true) .addDefaultProperty("skin_jacket", true)
.addDefaultProperty("skin_left_sleeve", true) .addDefaultProperty("skin_left_sleeve", true)
@ -357,7 +357,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
.setHologramOffset(-1.675)); .setHologramOffset(-1.675));
register(builder(p, "warden", EntityTypes.WARDEN) register(builder(p, "warden", EntityTypes.WARDEN)
.setHologramOffset(0.925)); .setHologramOffset(0.925)
.addProperties("warden_anger"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return; if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
@ -368,6 +369,12 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "camel", EntityTypes.CAMEL) register(builder(p, "camel", EntityTypes.CAMEL)
.setHologramOffset(0.25) .setHologramOffset(0.25)
.addProperties("bashing", "camel_sitting")); .addProperties("bashing", "camel_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
register(builder(p, "armadillo", EntityTypes.ARMADILLO)
.setHologramOffset(-1.475)
.addProperties("armadillo_state"));
} }
public Collection<NpcType> getAll() { public Collection<NpcType> getAll() {

@ -14,21 +14,27 @@ import java.util.stream.Collectors;
* pre-1.17 had all of their classes "flattened" into one package. * pre-1.17 had all of their classes "flattened" into one package.
*/ */
public class ReflectionPackage { public class ReflectionPackage {
private static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; private static final String VERSION = generateVersion();
public static final String BUKKIT = "org.bukkit.craftbukkit." + VERSION; public static final String BUKKIT = "org.bukkit.craftbukkit" + VERSION;
private static final boolean flattened = !PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17); private static final boolean flattened = !PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17);
/** /**
* Check if the classes are flattened, if so we need to add the version string into the * Check if the classes are flattened, if so we need to add the version string into the
* package string which is another quirk of the old server jars. * package string which is another quirk of the old server jars.
*/ */
public static final String MINECRAFT = joinWithDot("net.minecraft", flattened ? "server." + VERSION : ""); public static final String MINECRAFT = joinWithDot("net.minecraft", flattened ? "server" + VERSION : "");
public static final String ENTITY = flattened ? MINECRAFT : joinWithDot(MINECRAFT, "world.entity"); public static final String ENTITY = flattened ? MINECRAFT : joinWithDot(MINECRAFT, "world.entity");
public static String joinWithDot(String... parts) { public static String joinWithDot(String... parts) {
return Arrays.stream(parts) return Arrays.stream(parts)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(p -> p.length() != 0) .filter(p -> !p.isEmpty())
.collect(Collectors.joining(".")); .collect(Collectors.joining("."));
} }
private static String generateVersion() {
String[] parts = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
if (parts.length > 3) return "." + parts[3];
return "";
}
} }