upstream #1

Closed
bridge wants to merge 65 commits from feat/upstream into 2.X
60 changed files with 1454 additions and 287 deletions

18
.drone.yml Normal file
View file

@ -0,0 +1,18 @@
kind: pipeline
type: docker
name: default
trigger:
event:
- push
- custom
steps:
- name: publish
pull: if-not-exists
image: openjdk:21-jdk
environment:
PACKAGESKEY:
from_secret: GITEA_PACKAGE_PUBLIC_RW
commands:
- ./gradlew --no-daemon --parallel -Pnetherite.git.packages.token=$PACKAGESKEY build publish

View file

@ -40,7 +40,7 @@ Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/i
## Credits
- [PacketEvents 2.0](https://github.com/retrooper/packetevents) - Packet library
- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation
- [Minecraft Wiki Protocol (formally wiki.vg)](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Main_Page) - Minecraft protocol documentation
- [gson](https://github.com/google/gson) - JSON parsing library made by Google
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
- [adventure](https://docs.advntr.dev/) - Minecraft text api

View file

@ -1,6 +1,5 @@
plugins {
id "java"
id "maven-publish"
}
java {
@ -21,15 +20,4 @@ publishing {
}
}
}
repositories {
maven {
Map<String, String> systemProperties = System.getenv()
credentials {
if (systemProperties.containsKey("DIST_USERNAME")) username systemProperties.get("DIST_USERNAME")
if (systemProperties.containsKey("DIST_PASSWORD")) password systemProperties.get("DIST_PASSWORD")
}
// If the BUILD_ID enviroment variable is present that means its a Jenkins build & that it should go into the snapshots repo
url = systemProperties.containsKey("BUILD_ID") ? uri("https://repo.pyr.lol/snapshots/") : uri("https://repo.pyr.lol/releases/")
}
}
}

View file

@ -5,6 +5,7 @@ import lol.pyr.znpcsplus.api.interaction.ActionFactory;
import lol.pyr.znpcsplus.api.interaction.ActionRegistry;
import lol.pyr.znpcsplus.api.npc.NpcRegistry;
import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry;
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
/**
@ -46,4 +47,10 @@ public interface NpcApi {
* @return the skin descriptor factory
*/
SkinDescriptorFactory getSkinDescriptorFactory();
/**
* Gets the npc serializer registry.
* @return the npc serializer registry
*/
NpcSerializerRegistry getNpcSerializerRegistry();
}

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.api.interaction;
public interface ActionRegistry {
void register(InteractionActionType<?> type);
void unregister(Class<? extends InteractionAction> clazz);
<T extends InteractionAction> T deserialize(String str);
<T extends InteractionAction> String serialize(T action);
}

View file

@ -6,10 +6,12 @@ import lol.pyr.znpcsplus.api.interaction.InteractionAction;
import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* Base class for all NPCs
@ -135,14 +137,16 @@ public interface Npc extends PropertyHolder {
/**
* Shows this NPC to a player
* @param player The {@link Player} to show to
* @return A future that completes when the npc is fully shown to the player
*/
void show(Player player);
CompletableFuture<Void> show(Player player);
/**
* Respawns this NPC for a player
* @param player The {@link Player} to respawn for
* @return A future that completes when the npc is fully respawned
*/
void respawn(Player player);
CompletableFuture<Void> respawn(Player player);
/**
* Sets the head rotation of this NPC for a player
@ -174,4 +178,35 @@ public interface Npc extends PropertyHolder {
* @param offHand Should the hand be the offhand
*/
void swingHand(boolean offHand);
/**
* Gets the passengers of this npc
* @return The list of entity ids of the passengers
*/
@Nullable List<Integer> getPassengers();
/**
* Adds a passenger to this npc
* @param entityId The entity id of the passenger to add
*/
void addPassenger(int entityId);
/**
* Removes a passenger from this npc
* @param entityId The entity id of the passenger to remove
*/
void removePassenger(int entityId);
/**
* Gets the vehicle entity id of this npc
* @return The entity id of the vehicle
*/
@Nullable Integer getVehicleId();
/**
* Sets the vehicle id of this npc
* @param vehicleId The entity id of the vehicle
*/
void setVehicleId(Integer vehicleId);
}

View file

@ -64,4 +64,21 @@ public interface NpcRegistry {
* @param id The ID of the NPC entry
*/
void delete(String id);
/**
* Register an NPC to this registry
* NpcEntry instances can be obtained through the NpcSerializer classes
* @param entry The npc to be registered
*/
void register(NpcEntry entry);
/**
* Reload all saveable npcs from storage
*/
void reload();
/**
* Save all saveable npcs to storage
*/
void save();
}

View file

@ -0,0 +1,20 @@
package lol.pyr.znpcsplus.api.serialization;
import lol.pyr.znpcsplus.api.npc.NpcEntry;
public interface NpcSerializer<T> {
/**
* Serialize an npc into the type of this serializer
* @param entry The npc entry
* @return The serialized class
*/
T serialize(NpcEntry entry);
/**
* Deserialize an npc from a serialized class
* Note: This npc will not be registered, you need to also register it using the NpcRegistry#register(NpcEntry) method
* @param model The serialized class
* @return The deserialized NpcEntry
*/
NpcEntry deserialize(T model);
}

View file

@ -0,0 +1,19 @@
package lol.pyr.znpcsplus.api.serialization;
public interface NpcSerializerRegistry {
/**
* Get an NpcSerializer that serializes npcs into the provided class
* @param clazz The class to serialize into
* @return The npc serializer instance
* @param <T> The type of the class that the serializer serializes into
*/
<T> NpcSerializer<T> getSerializer(Class<T> clazz);
/**
* Register an NpcSerializer to be used by other plugins
* @param clazz The class that the serializer serializes into
* @param serializer The serializer itself
* @param <T> The type of the class that the serializer serializes into
*/
<T> void registerSerializer(Class<T> clazz, NpcSerializer<T> serializer);
}

View file

@ -12,4 +12,5 @@ public interface SkinDescriptorFactory {
SkinDescriptor createStaticDescriptor(String texture, String signature);
SkinDescriptor createUrlDescriptor(String url, String variant);
SkinDescriptor createUrlDescriptor(URL url, String variant);
SkinDescriptor createFileDescriptor(String path);
}

View file

@ -0,0 +1,11 @@
package lol.pyr.znpcsplus.util;
public enum SkeletonType {
NORMAL,
WITHER,
STRAY;
public byte getLegacyId() {
return (byte) ordinal();
}
}

View file

@ -2,13 +2,14 @@ subprojects {
apply plugin: "java"
group "lol.pyr"
version "2.0.0" + (System.getenv().containsKey("BUILD_ID") ? "-SNAPSHOT" : "")
version "2.0.0-netherite-SNAPSHOT"
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(8))
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
dependencies {
compileOnly "org.jetbrains:annotations:26.0.1"
compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
}
@ -20,6 +21,9 @@ subprojects {
maven {
url "https://repo.codemc.io/repository/maven-releases/"
}
maven {
url "https://repo.codemc.io/repository/maven-snapshots/"
}
maven {
url "https://libraries.minecraft.net"
}
@ -32,5 +36,23 @@ subprojects {
maven {
url "https://repo.pyr.lol/releases"
}
maven {
url "https://jitpack.io/"
}
}
publishing {
repositories {
maven {
url = uri("https://git.netherite.gg/api/packages/Netherite-Public/maven")
credentials(HttpHeaderCredentials) {
name "Authorization"
value "token ${project.properties["netherite.git.packages.token"]}"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
}

0
gradlew vendored Normal file → Executable file
View file

View file

@ -8,32 +8,65 @@ runServer {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
minecraftVersion "1.21.3"
minecraftVersion "1.21.4"
}
processResources {
expand("version": version)
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifactId = "znpcsplus-plugin"
pom {
name.set("znpcsplus-plugin")
description.set("The ZNPCsPlus plugin")
url.set("https://github.com/Pyrbu/ZNPCsPlus")
}
}
}
}
dependencies {
compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support
implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
implementation "com.github.retrooper:packetevents-spigot:2.6.0" // Packets
implementation "com.github.retrooper:packetevents-spigot:2.7.0" // Packets
implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
implementation "lol.pyr:director-adventure:2.1.2" // Commands
// Fancy text library
implementation "net.kyori:adventure-platform-bukkit:4.3.4"
implementation "net.kyori:adventure-text-minimessage:4.17.0"
implementation "net.kyori:adventure-text-minimessage:4.18.0"
implementation project(":api")
}
ext {
gitBranch = System.getenv('GIT_BRANCH') ?: ''
gitCommitHash = System.getenv('GIT_COMMIT') ?: ''
buildId = System.getenv('BUILD_ID') ?: ''
}
shadowJar {
archivesBaseName = "ZNPCsPlus"
archiveClassifier.set ""
manifest {
if (gitBranch?.trim()) {
attributes('Git-Branch': gitBranch)
}
if (gitCommitHash?.trim()) {
attributes('Git-Commit': gitCommitHash)
}
if (buildId?.trim()) {
attributes('Build-Id': buildId)
}
}
relocate "org.objectweb.asm", "lol.pyr.znpcsplus.libraries.asm"
relocate "me.lucko.jarrelocator", "lol.pyr.znpcsplus.libraries.jarrelocator"

View file

@ -38,6 +38,7 @@ import lol.pyr.znpcsplus.parsers.*;
import lol.pyr.znpcsplus.scheduling.FoliaScheduler;
import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.storage.NpcStorageType;
@ -93,7 +94,7 @@ public class ZNpcsPlus {
packetEvents.load();
configManager = new ConfigManager(getDataFolder());
skinCache = new MojangSkinCache(configManager);
skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
NpcPropertyRegistryProvider.register(propertyRegistry);
@ -135,8 +136,9 @@ public class ZNpcsPlus {
ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
ActionFactoryImpl actionFactory = new ActionFactoryImpl(scheduler, adventure, textSerializer, bungeeConnector);
NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl();
NpcSerializerRegistryImpl serializerRegistry = new NpcSerializerRegistryImpl(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
NpcRegistryImpl npcRegistry = new NpcRegistryImpl(configManager, this, packetFactory, actionRegistry,
scheduler, typeRegistry, propertyRegistry, textSerializer);
scheduler, typeRegistry, propertyRegistry, serializerRegistry, textSerializer);
shutdownTasks.add(npcRegistry::unload);
UserManager userManager = new UserManager();
@ -159,7 +161,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory, serializerRegistry);
log(ChatColor.WHITE + " * Starting tasks...");
if (configManager.getConfig().checkForUpdates()) {
@ -193,7 +195,7 @@ public class ZNpcsPlus {
}
}
NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache));
NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache, serializerRegistry));
log(ChatColor.WHITE + " * Loading complete! (" + (System.currentTimeMillis() - before) + "ms)");
log("");
@ -246,7 +248,7 @@ public class ZNpcsPlus {
private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
ConfigManager configManager, PacketFactory packetFactory) {
ConfigManager configManager, PacketFactory packetFactory, NpcSerializerRegistryImpl serializerRegistry) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
@ -296,6 +298,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
registerEnumParser(manager, SkeletonType.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))
@ -321,7 +324,7 @@ public class ZNpcsPlus {
.addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry, serializerRegistry)))
.addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
.addSubcommand("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
@ -332,13 +335,16 @@ public class ZNpcsPlus {
.addSubcommand("set", new HoloSetCommand(npcRegistry))
.addSubcommand("setitem", new HoloSetItemCommand(npcRegistry))
.addSubcommand("offset", new HoloOffsetCommand(npcRegistry))
.addSubcommand("refreshdelay", new HoloRefreshDelayCommand(npcRegistry)))
.addSubcommand("refreshdelay", new HoloRefreshDelayCommand(npcRegistry))
.addSubcommand("removeduplicate", new HoloRemoveDuplicateCommand(npcRegistry))
.addSubcommand("removeallduplicates", new holoRemoveAllDuplicatesCommand(npcRegistry)))
.addSubcommand("action", new MultiCommand(bootstrap.loadHelpMessage("action"))
.addSubcommand("add", new ActionAddCommand(npcRegistry, actionRegistry))
.addSubcommand("clear", new ActionClearCommand(npcRegistry))
.addSubcommand("delete", new ActionDeleteCommand(npcRegistry))
.addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry))
.addSubcommand("list", new ActionListCommand(npcRegistry)))
.addSubcommand("version", new VersionCommand(this))
);
}

View file

@ -12,6 +12,7 @@ import lol.pyr.znpcsplus.interaction.ActionFactoryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.skin.SkinDescriptorFactoryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
@ -22,14 +23,16 @@ public class ZNpcsPlusApi implements NpcApi {
private final ActionRegistryImpl actionRegistry;
private final ActionFactoryImpl actionFactory;
private final SkinDescriptorFactoryImpl skinDescriptorFactory;
private final NpcSerializerRegistryImpl npcSerializerRegistry;
public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache) {
public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache, NpcSerializerRegistryImpl npcSerializerRegistry) {
this.npcRegistry = npcRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.actionRegistry = actionRegistry;
this.actionFactory = actionFactory;
this.skinDescriptorFactory = new SkinDescriptorFactoryImpl(skinCache);
this.npcSerializerRegistry = npcSerializerRegistry;
}
@Override
@ -62,4 +65,9 @@ public class ZNpcsPlusApi implements NpcApi {
public SkinDescriptorFactory getSkinDescriptorFactory() {
return skinDescriptorFactory;
}
@Override
public NpcSerializerRegistryImpl getNpcSerializerRegistry() {
return npcSerializerRegistry;
}
}

View file

@ -26,12 +26,18 @@ public class CreateCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " create <id> <type>");
context.setUsage(context.getLabel() + " create <id> [<type>]");
Player player = context.ensureSenderIsPlayer();
String id = context.popString();
if (npcRegistry.getById(id) != null) context.halt(Component.text("NPC with that ID already exists.", NamedTextColor.RED));
NpcTypeImpl type = context.parse(NpcTypeImpl.class);
NpcTypeImpl type;
if (context.argSize() == 1) {
type = context.parse(NpcTypeImpl.class);
} else {
type = typeRegistry.getByName("player");
}
NpcEntryImpl entry = npcRegistry.create(id, player.getWorld(), type, new NpcLocation(player.getLocation()));
entry.enableEverything();

View file

@ -17,10 +17,14 @@ import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class SkinCommand implements CommandHandler {
private final MojangSkinCache skinCache;
@ -90,6 +94,30 @@ public class SkinCommand implements CommandHandler {
context.send(Component.text("Invalid url!", NamedTextColor.RED));
}
return;
} else if (type.equalsIgnoreCase("file")) {
context.ensureArgsNotEmpty();
String path = context.dumpAllArgs();
context.send(Component.text("Fetching skin from file \"" + path + "\"...", NamedTextColor.GREEN));
PrefetchedDescriptor.fromFile(skinCache, path).exceptionally(e -> {
if (e instanceof FileNotFoundException || e.getCause() instanceof FileNotFoundException) {
context.send(Component.text("A file at the specified path could not be found!", NamedTextColor.RED));
} else {
context.send(Component.text("An error occurred while fetching the skin from file! Check the console for more details.", NamedTextColor.RED));
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
return null;
}).thenAccept(skin -> {
if (skin == null) return;
if (skin.getSkin() == null) {
context.send(Component.text("Failed to fetch skin, are you sure the file path is valid?", NamedTextColor.RED));
return;
}
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), skin);
npc.respawn();
context.send(Component.text("The NPC's skin has been set.", NamedTextColor.GREEN));
});
return;
}
context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
}
@ -97,11 +125,19 @@ public class SkinCommand implements CommandHandler {
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url");
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
return context.suggestLiteral("slim", "classic");
}
if (context.argSize() == 3 && context.matchSuggestion("*", "file")) {
if (skinCache.getSkinsFolder().exists()) {
File[] files = skinCache.getSkinsFolder().listFiles();
if (files != null) {
return Arrays.stream(files).map(File::getName).collect(Collectors.toList());
}
}
}
return Collections.emptyList();
}
}

View file

@ -21,9 +21,14 @@ public class ToggleCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " toggle <id>");
context.setUsage(context.getLabel() + " toggle <id> [<enable/disable>]");
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
boolean enabled = !npc.isEnabled();
boolean enabled;
if (context.argSize() == 1) {
enabled = context.popString().equalsIgnoreCase("enable");
} else {
enabled = !npc.isEnabled();
}
npc.setEnabled(enabled);
context.send(Component.text("NPC has been " + (enabled ? "enabled" : "disabled"), NamedTextColor.GREEN));
}
@ -31,6 +36,7 @@ public class ToggleCommand implements CommandHandler {
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("enable", "disable");
return Collections.emptyList();
}
}

View file

@ -0,0 +1,64 @@
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.ZNpcsPlus;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
public class VersionCommand implements CommandHandler {
private final String pluginVersion;
private final String gitBranch;
private final String gitCommitHash;
private final String buildId;
public VersionCommand(ZNpcsPlus plugin) {
pluginVersion = plugin.getDescription().getVersion();
String gitBranch = "";
String gitCommitHash = "";
String buildId = "";
try {
URL jarUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
JarFile jarFile = new JarFile(jarUrl.toURI().getPath());
Attributes attributes = jarFile.getManifest().getMainAttributes();
gitBranch = attributes.getValue("Git-Branch");
gitCommitHash = attributes.getValue("Git-Commit-Hash");
buildId = attributes.getValue("Build-Id");
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
this.gitBranch = gitBranch;
this.gitCommitHash = gitCommitHash;
this.buildId = buildId;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
StringBuilder versionBuilder = new StringBuilder("This server is running ZNPCsPlus version ").append(pluginVersion);
if (gitBranch != null && !gitBranch.isEmpty()) {
versionBuilder.append("-").append(gitBranch);
}
if (gitCommitHash != null && !gitCommitHash.isEmpty()) {
versionBuilder.append("@").append(gitCommitHash);
}
if (buildId != null && !buildId.isEmpty()) {
versionBuilder.append(" (Build #").append(buildId).append(")");
}
String version = versionBuilder.toString();
context.send(Component.text(version, NamedTextColor.GREEN)
.hoverEvent(Component.text("Click to copy version to clipboard"))
.clickEvent(ClickEvent.copyToClipboard(version)));
}
}

View file

@ -0,0 +1,58 @@
package lol.pyr.znpcsplus.commands.hologram;
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.hologram.HologramImpl;
import lol.pyr.znpcsplus.hologram.HologramLine;
import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class HoloRemoveDuplicateCommand implements CommandHandler {
private final NpcRegistryImpl registry;
public HoloRemoveDuplicateCommand(NpcRegistryImpl registry) {
this.registry = registry;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " holo removeduplicates <id>");
HologramImpl hologram = context.parse(NpcEntryImpl.class).getNpc().getHologram();
List<HologramLine<?>> lines = new ArrayList<>(hologram.getLines());
List<HologramText> textLines = new ArrayList<>();
Iterator<HologramLine<?>> iterator = lines.iterator();
while (iterator.hasNext()) {
HologramLine<?> line = iterator.next();
if (line instanceof HologramText textLine
&& !textLine.getValue().equals(Component.empty())
&& textLines.contains(textLine)) {
iterator.remove();
continue;
}
if (line instanceof HologramText textLine) {
textLines.add(textLine);
}
}
hologram.clearLines();
hologram.addLines(lines);
context.send(Component.text("NPC lines fixed!", NamedTextColor.GREEN));
}
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(registry.getModifiableIds());
return Collections.emptyList();
}
}

View file

@ -0,0 +1,62 @@
package lol.pyr.znpcsplus.commands.hologram;
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.hologram.HologramImpl;
import lol.pyr.znpcsplus.hologram.HologramLine;
import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class holoRemoveAllDuplicatesCommand implements CommandHandler {
private final NpcRegistryImpl registry;
public holoRemoveAllDuplicatesCommand(NpcRegistryImpl registry) {
this.registry = registry;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " holo removeallduplicates <id>");
for (NpcEntryImpl npcEntry : registry.getAll()) {
HologramImpl hologram = npcEntry.getNpc().getHologram();
List<HologramLine<?>> lines = new ArrayList<>(hologram.getLines());
List<HologramText> textLines = new ArrayList<>();
Iterator<HologramLine<?>> iterator = lines.iterator();
while (iterator.hasNext()) {
HologramLine<?> line = iterator.next();
if (line instanceof HologramText textLine
&& !textLine.getValue().equals(Component.empty())
&& textLines.contains(textLine)) {
iterator.remove();
continue;
}
if (line instanceof HologramText textLine) {
textLines.add(textLine);
}
}
hologram.clearLines();
hologram.addLines(lines);
}
context.send(Component.text("NPCs fixed!", NamedTextColor.GREEN));
}
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(registry.getModifiableIds());
return Collections.emptyList();
}
}

View file

@ -11,6 +11,7 @@ import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
@ -124,6 +125,15 @@ public class PropertySetCommand implements CommandHandler {
value = context.parse(type);
valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString();
}
else if (property instanceof AttributeProperty) {
value = context.parse(type);
if ((double) value < ((AttributeProperty) property).getMinValue() || (double) value > ((AttributeProperty) property).getMaxValue()) {
double sanitizedValue = ((AttributeProperty) property).sanitizeValue((double) value);
context.send(Component.text("WARNING: Value " + value + " is out of range for property " + property.getName() + ", setting to " + sanitizedValue, NamedTextColor.YELLOW));
value = sanitizedValue;
}
valueName = String.valueOf(value);
}
else {
try {
value = context.parse(type);

View file

@ -7,13 +7,13 @@ import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ImportCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -33,7 +33,7 @@ public class ImportCommand implements CommandHandler {
if (importer == null) context.halt(Component.text("Importer not found! Possible importers: " +
String.join(", ", importerRegistry.getIds()), NamedTextColor.RED));
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
if (!importer.isValid()) {
context.send(Component.text("There is no data to import from this importer!", NamedTextColor.RED));
return;

View file

@ -4,12 +4,12 @@ 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.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class LoadAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class LoadAllCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.reload();
context.send(Component.text("All NPCs have been re-loaded from storage", NamedTextColor.GREEN));
});

View file

@ -11,6 +11,7 @@ import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import net.kyori.adventure.text.Component;
@ -35,8 +36,9 @@ public class MigrateCommand implements CommandHandler {
private final NpcStorage currentStorage;
private final NpcStorageType currentStorageType;
private final NpcRegistryImpl npcRegistry;
private final NpcSerializerRegistryImpl serializerRegistry;
public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry, NpcSerializerRegistryImpl serializerRegistry) {
this.configManager = configManager;
this.plugin = plugin;
this.packetFactory = packetFactory;
@ -47,6 +49,7 @@ public class MigrateCommand implements CommandHandler {
this.currentStorage = currentStorage;
this.currentStorageType = currentStorageType;
this.npcRegistry = npcRegistry;
this.serializerRegistry = serializerRegistry;
}
@Override
@ -63,7 +66,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == from) {
fromStorage = currentStorage;
} else {
fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (fromStorage == null) {
context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
return;
@ -84,7 +87,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == to) {
toStorage = currentStorage;
} else {
toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (toStorage == null) {
context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
return;

View file

@ -4,12 +4,12 @@ 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.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class SaveAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class SaveAllCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.save();
context.send(Component.text("All NPCs have been saved to storage", NamedTextColor.GREEN));
});

View file

@ -189,12 +189,15 @@ public class ZNpcImporter implements DataImporter {
if (toggleValues.containsKey("mirror")) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache));
}
if (toggleValues.containsKey("glow")) {
try {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf((String) toggleValues.get("glow")));
} catch (IllegalArgumentException e) {
if (toggleValues.containsKey("glow") && (boolean) toggleValues.get("glow")) {
if (!model.getGlowName().isEmpty())
try {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf(model.getGlowName()));
} catch (IllegalArgumentException e) {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
}
else
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
}
}
}

View file

@ -84,4 +84,8 @@ public class ZNpcsModel {
public String getSignature() {
return signature;
}
public String getGlowName() {
return glowName;
}
}

View file

@ -0,0 +1,76 @@
package lol.pyr.znpcsplus.entity;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Represents an armor stand vehicle entity.
* <p>
* This entity is used to make the NPC sit on an invisible armor stand.
* </p>
*/
public class ArmorStandVehicleProperties implements PropertyHolder {
private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
public ArmorStandVehicleProperties(EntityPropertyRegistryImpl propertyRegistry) {
_setProperty(propertyRegistry.getByName("small", Boolean.class), true);
_setProperty(propertyRegistry.getByName("invisible", Boolean.class), true);
_setProperty(propertyRegistry.getByName("base_plate", Boolean.class), false);
}
@SuppressWarnings("unchecked")
public <T> T getProperty(EntityProperty<T> key) {
return hasProperty(key) ? (T) propertyMap.get((EntityPropertyImpl<?>) key) : key.getDefaultValue();
}
@Override
public boolean hasProperty(EntityProperty<?> key) {
return propertyMap.containsKey((EntityPropertyImpl<?>) key);
}
@SuppressWarnings("unchecked")
private <T> void _setProperty(EntityProperty<T> key, T value) {
Object val = value;
if (val instanceof ItemStack) val = SpigotConversionUtil.fromBukkitItemStack((ItemStack) val);
setProperty((EntityPropertyImpl<T>) key, (T) val);
}
@Override
public <T> void setProperty(EntityProperty<T> key, T value) {
throw new UnsupportedOperationException("Cannot set properties on armor stands");
}
@Override
public void setItemProperty(EntityProperty<?> key, ItemStack value) {
throw new UnsupportedOperationException("Cannot set item properties on armor stands");
}
@Override
public ItemStack getItemProperty(EntityProperty<?> key) {
throw new UnsupportedOperationException("Cannot get item properties on armor stands");
}
public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
if (key == null) return;
if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key);
else propertyMap.put(key, value);
}
public Set<EntityProperty<?>> getAllProperties() {
return Collections.unmodifiableSet(propertyMap.keySet());
}
@Override
public Set<EntityProperty<?>> getAppliedProperties() {
return Collections.unmodifiableSet(propertyMap.keySet());
}
}

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.entity;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
@ -15,6 +16,7 @@ import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.properties.*;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerLevelProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
@ -32,18 +34,19 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* 1.8 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7415">...</a>
* 1.9 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7968">...</a>
* 1.10 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8241">...</a>
* 1.11 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8534">...</a>
* 1.12 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14048">...</a>
* 1.13 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14800">...</a>
* 1.14 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15240">...</a>
* 1.15 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15991">...</a>
* 1.16 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=16539">...</a>
* 1.17 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=17521">...</a>
* 1.18-1.19 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=18191">...</a>
* 1.20 <a href="https://wiki.vg/index.php?title=Entity_metadata">...</a>
* 1.8 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2767708">...</a>
* 1.9 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768074">...</a>
* 1.10 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768201">...</a>
* 1.11 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768444">...</a>
* 1.12 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768647">...</a>
* 1.13 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768701">...</a>
* 1.14 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768716">...</a>
* 1.15 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768877">...</a>
* 1.16 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769100">...</a>
* 1.17 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769318">...</a>
* 1.18-1.19 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769409">...</a>
* 1.20 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769476">...</a>
* 1.21 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata">...</a>
*/
@SuppressWarnings("unchecked")
public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
@ -90,6 +93,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerEnumSerializer(Sound.class);
registerEnumSerializer(ArmadilloState.class);
registerEnumSerializer(WoldVariant.class);
registerEnumSerializer(SkeletonType.class);
registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
@ -124,6 +128,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new DummyProperty<>("look", LookType.FIXED));
register(new DummyProperty<>("look_distance", configManager.getConfig().lookPropertyDistance()));
register(new DummyProperty<>("look_return", false));
register(new DummyProperty<>("view_distance", configManager.getConfig().viewDistance()));
register(new DummyProperty<>("permission_required", false));
@ -151,6 +156,16 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
linkProperties("glow", "fire", "invisible");
register(new BooleanProperty("silent", 4, false, legacyBooleans));
// Attribute Max Health
register(new AttributeProperty(packetFactory, "attribute_max_health", Attributes.MAX_HEALTH));
// Health - LivingEntity
int healthIndex = 6;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) healthIndex = 9;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) healthIndex = 8;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) healthIndex = 7;
register(new HealthProperty(healthIndex));
final int tameableIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16;
@ -182,6 +197,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
}
register(new EntitySittingProperty(packetFactory, this));
// Player
register(new DummyProperty<>("skin", SkinDescriptor.class, false));
final int skinLayersIndex;
@ -436,6 +453,12 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later
register(new IntegerProperty("invulnerable_time", witherIndex, 0, false));
// Skeleton
if (ver.isOlderThan(ServerVersion.V_1_11)) {
if (legacyBooleans) register(new EncodedByteProperty<>("skeleton_type", SkeletonType.NORMAL, 13, SkeletonType::getLegacyId));
else register(new EncodedIntegerProperty<>("skeleton_type", SkeletonType.NORMAL, ver.isOlderThan(ServerVersion.V_1_10) ? 11 : 12, Enum::ordinal));
}
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
// Shulker
int shulkerIndex;
@ -483,7 +506,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) llamaIndex = 20;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) llamaIndex = 19;
else llamaIndex = 17;
register(new EncodedIntegerProperty<DyeColor>("carpet_color", DyeColor.class, llamaIndex++, obj -> obj == null ? -1 : obj.ordinal()));
// Removed in 1.21
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) register(new EncodedIntegerProperty<DyeColor>("carpet_color", DyeColor.class, llamaIndex++, obj -> obj == null ? -1 : obj.ordinal()));
register(new EncodedIntegerProperty<>("llama_variant", LlamaVariant.CREAMY, llamaIndex, Enum::ordinal));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
@ -663,8 +688,20 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
register(new EquipmentProperty(packetFactory, "body", EquipmentSlot.BODY));
// Bogged
register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_2)) return;
// Creaking
register(new BooleanProperty("creaking_active", 17, false, legacyBooleans));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_4)) return;
// Creaking
register(new BooleanProperty("creaking_crumbling", 18, false, legacyBooleans));
}
private void registerSerializer(PropertySerializer<?> serializer) {

View file

@ -8,27 +8,34 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class PacketEntity implements PropertyHolder {
private final PacketFactory packetFactory;
private final PropertyHolder properties;
private final Viewable viewable;
private final int entityId;
private final UUID uuid;
private final EntityType type;
private NpcLocation location;
public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, EntityType type, NpcLocation location) {
private PacketEntity vehicle;
private Integer vehicleId;
private List<Integer> passengers;
public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, Viewable viewable, EntityType type, NpcLocation location) {
this.packetFactory = packetFactory;
this.properties = properties;
this.viewable = viewable;
this.entityId = reserveEntityID();
this.uuid = UUID.randomUUID();
this.type = type;
@ -51,22 +58,117 @@ public class PacketEntity implements PropertyHolder {
return type;
}
public void setLocation(NpcLocation location, Collection<Player> viewers) {
public void setLocation(NpcLocation location) {
this.location = location;
for (Player viewer : viewers) packetFactory.teleportEntity(viewer, this);
if (vehicle != null) {
vehicle.setLocation(location.withY(location.getY() - 0.9));
return;
}
for (Player viewer : viewable.getViewers()) packetFactory.teleportEntity(viewer, this);
}
public void spawn(Player player) {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties);
else packetFactory.spawnEntity(player, this, properties);
public CompletableFuture<Void> spawn(Player player) {
return FutureUtil.exceptionPrintingRunAsync(() -> {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties).join();
else packetFactory.spawnEntity(player, this, properties);
if (vehicleId != null) {
packetFactory.setPassengers(player, vehicleId, this.getEntityId());
}
if (passengers != null) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
});
}
public void setHeadRotation(Player player, float yaw, float pitch) {
packetFactory.sendHeadRotation(player, this, yaw, pitch);
}
public PacketEntity getVehicle() {
return vehicle;
}
public Viewable getViewable() {
return viewable;
}
public void setVehicleId(Integer vehicleId) {
if (this.vehicle != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicle.getEntityId());
this.vehicle.despawn(player);
packetFactory.teleportEntity(player, this);
}
} else if (this.vehicleId != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicleId);
}
}
this.vehicleId = vehicleId;
if (vehicleId == null) return;
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), vehicleId);
}
}
public void setVehicle(PacketEntity vehicle) {
// remove old vehicle
if (this.vehicle != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicle.getEntityId());
this.vehicle.despawn(player);
packetFactory.teleportEntity(player, this);
}
} else if (this.vehicleId != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicleId);
}
}
this.vehicle = vehicle;
if (this.vehicle == null) return;
vehicle.setLocation(location.withY(location.getY() - 0.9));
for (Player player : viewable.getViewers()) {
vehicle.spawn(player).thenRun(() -> {
packetFactory.setPassengers(player, vehicle.getEntityId(), this.getEntityId());
});
}
}
public Integer getVehicleId() {
return vehicleId;
}
public List<Integer> getPassengers() {
return passengers == null ? Collections.emptyList() : passengers;
}
public void addPassenger(int entityId) {
if (passengers == null) {
passengers = new ArrayList<>();
}
passengers.add(entityId);
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
}
public void removePassenger(int entityId) {
if (passengers == null) return;
passengers.remove(entityId);
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
if (passengers.isEmpty()) {
passengers = null;
}
}
public void despawn(Player player) {
packetFactory.destroyEntity(player, this, properties);
if (vehicle != null) vehicle.despawn(player);
}
public void refreshMeta(Player player) {

View file

@ -0,0 +1,37 @@
package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import lol.pyr.znpcsplus.entity.ArmorStandVehicleProperties;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.packets.PacketFactory;
import org.bukkit.entity.Player;
import java.util.Map;
public class EntitySittingProperty extends EntityPropertyImpl<Boolean> {
private final PacketFactory packetFactory;
private final EntityPropertyRegistryImpl propertyRegistry;
public EntitySittingProperty(PacketFactory packetFactory, EntityPropertyRegistryImpl propertyRegistry) {
super("entity_sitting", false, Boolean.class);
this.packetFactory = packetFactory;
this.propertyRegistry = propertyRegistry;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
boolean sitting = entity.getProperty(this);
if (sitting) {
if (entity.getVehicle() == null) {
PacketEntity vehiclePacketEntity = new PacketEntity(packetFactory, new ArmorStandVehicleProperties(propertyRegistry),
entity.getViewable(), EntityTypes.ARMOR_STAND, entity.getLocation().withY(entity.getLocation().getY() - 0.9));
entity.setVehicle(vehiclePacketEntity);
}
} else if (entity.getVehicle() != null) {
entity.setVehicle(null);
}
}
}

View file

@ -24,7 +24,10 @@ public class GlowProperty extends EntityPropertyImpl<NamedColor> {
EntityData oldData = properties.get(0);
byte oldValue = oldData == null ? 0 : (byte) oldData.getValue();
properties.put(0, newEntityData(0, EntityDataTypes.BYTE, (byte) (oldValue | (value == null ? 0 : 0x40))));
if (isSpawned) packetFactory.removeTeam(player, entity);
packetFactory.createTeam(player, entity, value);
// the team is already created with the right glow color in the packet factory if the npc isnt spawned yet
if (isSpawned) {
packetFactory.removeTeam(player, entity);
packetFactory.createTeam(player, entity, value);
}
}
}

View file

@ -0,0 +1,27 @@
package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import org.bukkit.entity.Player;
import java.util.Map;
public class HealthProperty extends EntityPropertyImpl<Float> {
private final int index;
public HealthProperty(int index) {
super("health", 20f, Float.class);
this.index = index;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
float health = entity.getProperty(this);
health = (float) Attributes.MAX_HEALTH.sanitizeValue(health);
properties.put(index, new EntityData(index, EntityDataTypes.FLOAT, health));
}
}

View file

@ -0,0 +1,63 @@
package lol.pyr.znpcsplus.entity.properties.attributes;
import com.github.retrooper.packetevents.protocol.attribute.Attribute;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.packets.PacketFactory;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class AttributeProperty extends EntityPropertyImpl<Double> {
private final PacketFactory packetFactory;
private final Attribute attribute;
public AttributeProperty(PacketFactory packetFactory, String name, Attribute attribute) {
super(name, attribute.getDefaultValue(), Double.class);
this.packetFactory = packetFactory;
this.attribute = attribute;
}
public double getMinValue() {
return attribute.getMinValue();
}
public double getMaxValue() {
return attribute.getMaxValue();
}
public double sanitizeValue(double value) {
return attribute.sanitizeValue(value);
}
@Override
public List<EntityData> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
apply(player, packetEntity, isSpawned, Collections.emptyList());
return Collections.emptyList();
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
}
public void apply(Player player, PacketEntity entity, boolean isSpawned, List<WrapperPlayServerUpdateAttributes.Property> properties) {
Double value = entity.getProperty(this);
if (value == null) {
return;
}
value = attribute.sanitizeValue(value);
if (isSpawned) {
packetFactory.sendAttribute(player, entity, new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList()));
} else {
properties.add(new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList()));
}
}
public Attribute getAttribute() {
return attribute;
}
}

View file

@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.api.hologram.Hologram;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component;
@ -16,6 +17,8 @@ import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class HologramImpl extends Viewable implements Hologram {
private final ConfigManager configManager;
@ -38,9 +41,9 @@ public class HologramImpl extends Viewable implements Hologram {
}
public void addTextLineComponent(Component line) {
HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line);
HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
lines.add(newLine);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
@ -57,9 +60,9 @@ public class HologramImpl extends Viewable implements Hologram {
}
public void addItemLinePEStack(ItemStack item) {
HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item);
HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
lines.add(newLine);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
@ -98,10 +101,16 @@ public class HologramImpl extends Viewable implements Hologram {
lines.clear();
}
public void addLines(List<HologramLine<?>> lines) {
this.lines.addAll(lines);
relocateLines();
for (Player viewer : getViewers()) for (HologramLine<?> line : lines) line.show(viewer);
}
public void insertTextLineComponent(int index, Component line) {
HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line);
HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
lines.add(index, newLine);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
@ -114,9 +123,9 @@ public class HologramImpl extends Viewable implements Hologram {
}
public void insertItemLinePEStack(int index, ItemStack item) {
HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item);
HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
lines.add(index, newLine);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
@ -138,8 +147,10 @@ public class HologramImpl extends Viewable implements Hologram {
}
@Override
protected void UNSAFE_show(Player player) {
for (HologramLine<?> line : lines) line.show(player);
protected CompletableFuture<Void> UNSAFE_show(Player player) {
return FutureUtil.allOf(lines.stream()
.map(line -> line.show(player))
.collect(Collectors.toList()));
}
@Override
@ -172,14 +183,10 @@ public class HologramImpl extends Viewable implements Hologram {
}
private void relocateLines() {
relocateLines(null);
}
private void relocateLines(HologramLine<?> newLine) {
final double lineSpacing = configManager.getConfig().lineSpacing();
double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset();
for (HologramLine<?> line : lines) {
line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers());
line.setLocation(location.withY(height));
height -= lineSpacing;
}
}

View file

@ -15,13 +15,11 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.entity.Player;
import java.util.Collection;
import lol.pyr.znpcsplus.util.Viewable;
public class HologramItem extends HologramLine<ItemStack> {
public HologramItem(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
super(item, packetFactory, EntityTypes.ITEM, location);
public HologramItem(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
super(viewable, item, packetFactory, EntityTypes.ITEM, location);
addProperty(propertyRegistry.getByName("holo_item"));
}
@ -33,8 +31,8 @@ public class HologramItem extends HologramLine<ItemStack> {
}
@Override
public void setLocation(NpcLocation location, Collection<Player> viewers) {
super.setLocation(location.withY(location.getY() + 2.05), viewers);
public void setLocation(NpcLocation location) {
super.setLocation(location.withY(location.getY() + 2.05));
}
public static boolean ensureValidItemInput(String in) {

View file

@ -7,21 +7,22 @@ import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class HologramLine<M> implements PropertyHolder {
private M value;
private final PacketEntity entity;
private final Set<EntityProperty<?>> properties;
public HologramLine(M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
public HologramLine(Viewable viewable, M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
this.value = value;
this.entity = new PacketEntity(packetFactory, this, type, location);
this.entity = new PacketEntity(packetFactory, this, viewable, type, location);
this.properties = new HashSet<>();
}
@ -37,16 +38,16 @@ public class HologramLine<M> implements PropertyHolder {
entity.refreshMeta(player);
}
protected void show(Player player) {
entity.spawn(player);
protected CompletableFuture<Void> show(Player player) {
return entity.spawn(player);
}
protected void hide(Player player) {
entity.despawn(player);
}
public void setLocation(NpcLocation location, Collection<Player> viewers) {
entity.setLocation(location, viewers);
public void setLocation(NpcLocation location) {
entity.setLocation(location);
}
public int getEntityId() {

View file

@ -5,24 +5,26 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class HologramText extends HologramLine<Component> {
private static final Component BLANK = Component.text("%blank%");
public HologramText(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
super(text, packetFactory, EntityTypes.ARMOR_STAND, location);
public HologramText(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
super(viewable, text, packetFactory, EntityTypes.ARMOR_STAND, location);
addProperty(propertyRegistry.getByName("name"));
addProperty(propertyRegistry.getByName("invisible"));
}
@Override
public void show(Player player) {
if (!getValue().equals(BLANK)) {
super.show(player);
}
public CompletableFuture<Void> show(Player player) {
if (getValue().equals(BLANK)) return CompletableFuture.completedFuture(null);
return super.show(player);
}
@SuppressWarnings("unchecked")
@ -37,4 +39,12 @@ public class HologramText extends HologramLine<Component> {
public boolean hasProperty(EntityProperty<?> key) {
return key.getName().equalsIgnoreCase("name") || key.getName().equalsIgnoreCase("invisible");
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
HologramText that = (HologramText) obj;
return getValue().equals(that.getValue());
}
}

View file

@ -32,7 +32,7 @@ public class SwitchServerAction extends InteractionActionImpl {
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
Component.text("Click to edit this action", NamedTextColor.GRAY)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
"/" + context.getLabel() + " action edit " + id + " " + index + " switcserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server))
"/" + context.getLabel() + " action edit " + id + " " + index + " switchserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server))
.append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text("[DELETE]", NamedTextColor.RED)
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,

View file

@ -20,9 +20,12 @@ import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class NpcImpl extends Viewable implements Npc {
@ -38,6 +41,8 @@ public class NpcImpl extends Viewable implements Npc {
private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
private final List<InteractionAction> actions = new ArrayList<>();
private final Map<UUID, float[]> playerLookMap = new ConcurrentHashMap<>();
protected NpcImpl(UUID uuid, EntityPropertyRegistryImpl propertyRegistry, ConfigManager configManager, LegacyComponentSerializer textSerializer, World world, NpcTypeImpl type, NpcLocation location, PacketFactory packetFactory) {
this(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world.getName(), type, location);
}
@ -48,14 +53,14 @@ public class NpcImpl extends Viewable implements Npc {
this.type = type;
this.location = location;
this.uuid = uuid;
entity = new PacketEntity(packetFactory, this, type.getType(), location);
entity = new PacketEntity(packetFactory, this, this, type.getType(), location);
hologram = new HologramImpl(propertyRegistry, configManager, packetFactory, textSerializer, location.withY(location.getY() + type.getHologramOffset()));
}
public void setType(NpcTypeImpl type) {
UNSAFE_hideAll();
this.type = type;
entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation());
entity = new PacketEntity(packetFactory, this, this, type.getType(), entity.getLocation());
hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
UNSAFE_showAll();
}
@ -85,20 +90,34 @@ public class NpcImpl extends Viewable implements Npc {
public void setLocation(NpcLocation location) {
this.location = location;
entity.setLocation(location, getViewers());
playerLookMap.clear();
playerLookMap.putAll(getViewers().stream().collect(Collectors.toMap(Player::getUniqueId, player -> new float[]{location.getYaw(), location.getPitch()})));
entity.setLocation(location);
hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
}
public void setHeadRotation(Player player, float yaw, float pitch) {
if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) return;
playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
entity.setHeadRotation(player, yaw, pitch);
}
public void setHeadRotation(float yaw, float pitch) {
for (Player player : getViewers()) {
if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) continue;
playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
entity.setHeadRotation(player, yaw, pitch);
}
}
public float getHeadYaw(Player player) {
return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[0];
}
public float getHeadPitch(Player player) {
return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[1];
}
public HologramImpl getHologram() {
return hologram;
}
@ -125,13 +144,14 @@ public class NpcImpl extends Viewable implements Npc {
}
@Override
protected void UNSAFE_show(Player player) {
entity.spawn(player);
hologram.show(player);
protected CompletableFuture<Void> UNSAFE_show(Player player) {
playerLookMap.put(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()});
return CompletableFuture.allOf(entity.spawn(player), hologram.show(player));
}
@Override
protected void UNSAFE_hide(Player player) {
playerLookMap.remove(player.getUniqueId());
entity.despawn(player);
hologram.hide(player);
}
@ -246,4 +266,29 @@ public class NpcImpl extends Viewable implements Npc {
public void swingHand(boolean offHand) {
for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
}
@Override
public @NotNull List<Integer> getPassengers() {
return entity.getPassengers();
}
@Override
public void addPassenger(int entityId) {
entity.addPassenger(entityId);
}
@Override
public void removePassenger(int entityId) {
entity.removePassenger(entityId);
}
@Override
public @Nullable Integer getVehicleId() {
return entity.getVehicleId();
}
@Override
public void setVehicleId(Integer vehicleId) {
entity.setVehicleId(vehicleId);
}
}

View file

@ -14,6 +14,7 @@ import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import lol.pyr.znpcsplus.util.NpcLocation;
@ -35,13 +36,13 @@ public class NpcRegistryImpl implements NpcRegistry {
private final Map<String, NpcEntryImpl> npcIdLookupMap = new HashMap<>();
private final Map<UUID, NpcEntryImpl> npcUuidLookupMap = new HashMap<>();
public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, NpcSerializerRegistryImpl serializerRegistry, LegacyComponentSerializer textSerializer) {
this.textSerializer = textSerializer;
this.propertyRegistry = propertyRegistry;
storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (storage == null) {
Bukkit.getLogger().warning("Failed to initialize storage, falling back to YAML");
storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
}
this.packetFactory = packetFactory;
this.configManager = configManager;
@ -52,7 +53,13 @@ public class NpcRegistryImpl implements NpcRegistry {
}
}
@Override
public void register(NpcEntry entry) {
register((NpcEntryImpl) entry);
}
private void register(NpcEntryImpl entry) {
if (entry == null) throw new NullPointerException();
unregister(npcIdLookupMap.put(entry.getId(), entry));
unregister(npcUuidLookupMap.put(entry.getNpc().getUuid(), entry));
npcList.add(entry);

View file

@ -114,12 +114,15 @@ public class NpcTypeImpl implements NpcType {
public NpcTypeImpl build() {
ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
addProperties("fire", "invisible", "silent", "look", "look_distance", "view_distance",
addProperties("fire", "invisible", "silent", "look", "look_distance", "look_return", "view_distance",
"potion_color", "potion_ambient", "display_name", "permission_required",
"player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical",
"player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name",
"player_knockback_sound_volume", "player_knockback_sound_pitch");
if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone");
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) {
addProperties("health", "attribute_max_health");
}
// TODO: make this look nicer after completing the rest of the properties
if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");
if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) {
@ -143,6 +146,9 @@ public class NpcTypeImpl implements NpcType {
} else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) {
addProperties("has_chest");
}
if (version.isOlderThan(ServerVersion.V_1_11) && EntityTypes.isTypeInstanceOf(type, EntityTypes.SKELETON)) {
addProperties("skeleton_type");
}
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_EVO_ILLU_ILLAGER)) {
addProperties("spell");
}
@ -176,6 +182,11 @@ public class NpcTypeImpl implements NpcType {
addProperties("wolf_variant");
}
}
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_4)) {
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.CREAKING)) {
addProperties("creaking_crumbling");
}
}
return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties);
}
}

View file

@ -36,7 +36,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "player", EntityTypes.PLAYER)
.setHologramOffset(-0.15D)
.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", "force_body_rotation")
.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", "entity_sitting")
.addDefaultProperty("skin_cape", true)
.addDefaultProperty("skin_jacket", true)
.addDefaultProperty("skin_left_sleeve", true)
@ -82,7 +82,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "enderman", EntityTypes.ENDERMAN)
.setHologramOffset(0.925)
.addProperties("enderman_held_block", "enderman_screaming", "enderman_staring"));
.addProperties("enderman_held_block", "enderman_screaming", "enderman_staring", "entity_sitting"));
register(builder(p, "endermite", EntityTypes.ENDERMITE)
.setHologramOffset(-1.675));
@ -93,7 +93,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "giant", EntityTypes.GIANT)
.setHologramOffset(10.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "guardian", EntityTypes.GUARDIAN)
.setHologramOffset(-1.125)
@ -133,7 +134,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "skeleton", EntityTypes.SKELETON)
.setHologramOffset(0.015)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
.setHologramOffset(-0.375));
@ -169,14 +171,16 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "zombie", EntityTypes.ZOMBIE)
.setHologramOffset(-0.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "zombie_horse", EntityTypes.ZOMBIE_HORSE)
.setHologramOffset(-0.375));
register(builder(p, "zombified_piglin", EntityTypes.ZOMBIFIED_PIGLIN)
.setHologramOffset(-0.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
@ -203,21 +207,21 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "husk", EntityTypes.HUSK)
.setHologramOffset(-0.025)
.addEquipmentProperties());
register(builder(p, "polar_bear", EntityTypes.POLAR_BEAR)
.setHologramOffset(-0.575));
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "stray", EntityTypes.STRAY)
.setHologramOffset(0.015)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "evoker", EntityTypes.EVOKER)
.setHologramOffset(-0.025));
.setHologramOffset(-0.025)
.addProperties("entity_sitting"));
register(builder(p, "llama", EntityTypes.LLAMA)
.setHologramOffset(-0.105)
.addProperties("carpet_color", "llama_variant"));
.addProperties("carpet_color", "llama_variant", "body"));
register(builder(p, "vex", EntityTypes.VEX)
.setHologramOffset(-1.175)
@ -225,20 +229,23 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "vindicator", EntityTypes.VINDICATOR)
.setHologramOffset(-0.025)
.addProperties("celebrating"));
.addProperties("celebrating", "entity_sitting"));
register(builder(p, "wither_skeleton", EntityTypes.WITHER_SKELETON)
.setHologramOffset(0.425)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
.setHologramOffset(-0.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
register(builder(p, "illusioner", EntityTypes.ILLUSIONER)
.setHologramOffset(-0.025));
.setHologramOffset(-0.025)
.addProperties("entity_sitting"));
register(builder(p, "parrot", EntityTypes.PARROT)
.setHologramOffset(-1.075)
@ -255,7 +262,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "drowned", EntityTypes.DROWNED)
.setHologramOffset(-0.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "phantom", EntityTypes.PHANTOM)
.setHologramOffset(-1.475));
@ -291,7 +299,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "pillager", EntityTypes.PILLAGER)
.setHologramOffset(-0.025)
.addHandProperties()
.addProperties("pillager_charging"));
.addProperties("pillager_charging", "entity_sitting"));
register(builder(p, "ravager", EntityTypes.RAVAGER)
.setHologramOffset(0.225));
@ -319,11 +327,12 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "piglin", EntityTypes.PIGLIN)
.setHologramOffset(-0.025)
.addEquipmentProperties()
.addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing"));
.addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing", "entity_sitting"));
register(builder(p, "piglin_brute", EntityTypes.PIGLIN_BRUTE)
.setHologramOffset(-0.025)
.addEquipmentProperties());
.addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "strider", EntityTypes.STRIDER)
.setHologramOffset(-0.275)
@ -372,6 +381,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
.setHologramOffset(0.4)
.addProperties("bashing", "camel_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
register(builder(p, "armadillo", EntityTypes.ARMADILLO)
@ -382,10 +392,16 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "bogged", EntityTypes.BOGGED)
.setHologramOffset(0.015)
.addProperties("bogged_sheared"));
.addProperties("bogged_sheared", "entity_sitting"));
register(builder(p, "breeze", EntityTypes.BREEZE)
.setHologramOffset(-0.205));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_21_2)) return;
register(builder(p, "creaking", EntityTypes.CREAKING)
.setHologramOffset(0.725)
.addProperties("creaking_active"));
}
public Collection<NpcType> getAll() {

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.player.Equipment;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.util.NamedColor;
@ -11,7 +12,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface PacketFactory {
void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties);
CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties);
void spawnEntity(Player player, PacketEntity entity, PropertyHolder properties);
void destroyEntity(Player player, PacketEntity entity, PropertyHolder properties);
void teleportEntity(Player player, PacketEntity entity);
@ -24,4 +25,7 @@ public interface PacketFactory {
void sendMetadata(Player player, PacketEntity entity, List<EntityData> data);
void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch);
void sendHandSwing(Player player, PacketEntity entity, boolean offHand);
void setPassengers(Player player, int vehicle, int... passengers);
void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties);
void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property);
}

View file

@ -1,6 +1,7 @@
package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
@ -27,6 +28,7 @@ public class V1_17PacketFactory extends V1_8PacketFactory {
sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(),
npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
sendAllMetadata(player, entity, properties);
if (EntityTypes.isTypeInstanceOf(entity.getType(), EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties);
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
}
}

View file

@ -16,6 +16,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
@ -27,14 +28,15 @@ public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
}
@Override
public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
addTabPlayer(player, entity, properties).thenAccept(ignored -> {
public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
NpcLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(),
npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
sendAllMetadata(player, entity, properties);
sendAllAttributes(player, entity, properties);
scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
});
}

View file

@ -18,6 +18,7 @@ import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.util.NamedColor;
@ -47,14 +48,15 @@ public class V1_8PacketFactory implements PacketFactory {
}
@Override
public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
addTabPlayer(player, entity, properties).thenAccept(ignored -> {
public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
NpcLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
entity.getUuid(), npcLocationToVector(location), location.getYaw(), location.getPitch(), Collections.emptyList()));
sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
sendAllMetadata(player, entity, properties);
sendAllAttributes(player, entity, properties);
scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
});
}
@ -70,6 +72,7 @@ public class V1_8PacketFactory implements PacketFactory {
new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location),
location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
sendAllMetadata(player, entity, properties);
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties);
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
}
@ -153,6 +156,11 @@ public class V1_8PacketFactory implements PacketFactory {
sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), Collections.singletonList(equipment)));
}
@Override
public void setPassengers(Player player, int vehicleEntityId, int... passengers) {
sendPacket(player, new WrapperPlayServerSetPassengers(vehicleEntityId, passengers));
}
protected void sendPacket(Player player, PacketWrapper<?> packet) {
packetEvents.getPlayerManager().sendPacket(player, packet);
}
@ -182,4 +190,19 @@ public class V1_8PacketFactory implements PacketFactory {
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND :
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM));
}
@Override
public void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties) {
List<WrapperPlayServerUpdateAttributes.Property> attributesList = new ArrayList<>();
properties.getAppliedProperties()
.stream()
.filter(property -> property instanceof AttributeProperty)
.forEach(property -> ((AttributeProperty) property).apply(player, entity, false, attributesList));
sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), attributesList));
}
@Override
public void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property) {
sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), Collections.singletonList(property)));
}
}

View file

@ -0,0 +1,33 @@
package lol.pyr.znpcsplus.serialization;
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.HashMap;
import java.util.Map;
public class NpcSerializerRegistryImpl implements NpcSerializerRegistry {
private final Map<Class<?>, NpcSerializer<?>> serializerMap = new HashMap<>();
public NpcSerializerRegistryImpl(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
registerSerializer(YamlConfiguration.class, new YamlSerializer(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer));
}
@SuppressWarnings("unchecked")
@Override
public <T> NpcSerializer<T> getSerializer(Class<T> clazz) {
return (NpcSerializer<T>) serializerMap.get(clazz);
}
@Override
public <T> void registerSerializer(Class<T> clazz, NpcSerializer<T> serializer) {
serializerMap.put(clazz, serializer);
}
}

View file

@ -0,0 +1,154 @@
package lol.pyr.znpcsplus.serialization;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.npc.NpcEntry;
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PropertySerializer;
import lol.pyr.znpcsplus.hologram.HologramImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class YamlSerializer implements NpcSerializer<YamlConfiguration> {
private final static Logger logger = Logger.getLogger("YamlSerializer");
private final PacketFactory packetFactory;
private final ConfigManager configManager;
private final ActionRegistryImpl actionRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final LegacyComponentSerializer textSerializer;
public YamlSerializer(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
this.packetFactory = packetFactory;
this.configManager = configManager;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
}
@Override
public YamlConfiguration serialize(NpcEntry entry) {
YamlConfiguration config = new YamlConfiguration();
config.set("id", entry.getId());
config.set("is-processed", entry.isProcessed());
config.set("allow-commands", entry.isAllowCommandModification());
config.set("save", entry.isSave());
NpcImpl npc = (NpcImpl) entry.getNpc();
config.set("enabled", npc.isEnabled());
config.set("uuid", npc.getUuid().toString());
config.set("world", npc.getWorldName());
config.set("location", serializeLocation(npc.getLocation()));
config.set("type", npc.getType().getName());
for (EntityProperty<?> property : npc.getAllProperties()) try {
PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
continue;
}
config.set("properties." + property.getName(), serializer.UNSAFE_serialize(npc.getProperty(property)));
} catch (Exception exception) {
logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
exception.printStackTrace();
}
HologramImpl hologram = npc.getHologram();
if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset());
if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay());
List<String> lines = new ArrayList<>(npc.getHologram().getLines().size());
for (int i = 0; i < hologram.getLines().size(); i++) {
lines.add(hologram.getLine(i));
}
config.set("hologram.lines", lines);
config.set("actions", npc.getActions().stream()
.map(actionRegistry::serialize)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
return config;
}
@Override
public NpcEntry deserialize(YamlConfiguration config) {
UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID();
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, config.getString("world"),
typeRegistry.getByName(config.getString("type")), deserializeLocation(config.getConfigurationSection("location")));
if (config.isBoolean("enabled")) npc.setEnabled(config.getBoolean("enabled"));
ConfigurationSection properties = config.getConfigurationSection("properties");
if (properties != null) {
for (String key : properties.getKeys(false)) {
EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
if (property == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
Object value = serializer.deserialize(properties.getString(key));
if (value == null) {
Bukkit.getLogger().log(Level.WARNING, "Failed to deserialize property '" + key + "' for npc '" + config.getString("id") + "'. Resetting to default ...");
value = property.getDefaultValue();
}
npc.UNSAFE_setProperty(property, value);
}
}
HologramImpl hologram = npc.getHologram();
hologram.setOffset(config.getDouble("hologram.offset", 0.0));
hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1));
for (String line : config.getStringList("hologram.lines")) hologram.addLine(line);
for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s));
NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
entry.setProcessed(config.getBoolean("is-processed"));
entry.setAllowCommandModification(config.getBoolean("allow-commands"));
entry.setSave(config.getBoolean("save", true));
return entry;
}
public NpcLocation deserializeLocation(ConfigurationSection section) {
return new NpcLocation(
section.getDouble("x"),
section.getDouble("y"),
section.getDouble("z"),
(float) section.getDouble("yaw"),
(float) section.getDouble("pitch")
);
}
public YamlConfiguration serializeLocation(NpcLocation location) {
YamlConfiguration config = new YamlConfiguration();
config.set("x", location.getX());
config.set("y", location.getY());
config.set("z", location.getZ());
config.set("yaw", location.getYaw());
config.set("pitch", location.getPitch());
return config;
}
}

View file

@ -52,4 +52,9 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
public SkinDescriptor createUrlDescriptor(URL url, String variant) {
return PrefetchedDescriptor.fromUrl(skinCache, url, variant).join();
}
@Override
public SkinDescriptor createFileDescriptor(String path) {
return PrefetchedDescriptor.fromFile(skinCache, path).join();
}
}

View file

@ -5,6 +5,7 @@ import com.google.gson.JsonParser;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -14,6 +15,7 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@ -26,9 +28,12 @@ public class MojangSkinCache {
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
private final File skinsFolder;
public MojangSkinCache(ConfigManager configManager) {
public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
this.configManager = configManager;
this.skinsFolder = skinsFolder;
if (!skinsFolder.exists()) skinsFolder.mkdirs();
}
public void cleanCache() {
@ -42,7 +47,7 @@ public class MojangSkinCache {
if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
return CompletableFuture.supplyAsync(() -> {
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://api.mojang.com/users/profiles/minecraft/" + name);
HttpURLConnection connection = null;
try {
@ -75,7 +80,7 @@ public class MojangSkinCache {
if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
return CompletableFuture.supplyAsync(() -> {
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://api.ashcon.app/mojang/v2/user/" + name);
HttpURLConnection connection = null;
try {
@ -106,7 +111,7 @@ public class MojangSkinCache {
}
public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) {
return CompletableFuture.supplyAsync(() -> {
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
HttpURLConnection connection = null;
try {
@ -142,6 +147,58 @@ public class MojangSkinCache {
});
}
public CompletableFuture<SkinImpl> fetchFromFile(String path) throws FileNotFoundException {
File file = new File(skinsFolder, path);
if (!file.exists()) throw new FileNotFoundException("File not found: " + path);
return CompletableFuture.supplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/upload");
HttpURLConnection connection = null;
try {
String boundary = "*****";
String CRLF = "\r\n";
connection = (HttpURLConnection) apiUrl.openConnection();
connection.setRequestMethod("POST");
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setUseCaches(false);
connection.setRequestProperty("Cache-Control", "no-cache");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
connection.setDoInput(true);
connection.setDoOutput(true);
OutputStream outputStream = connection.getOutputStream();
DataOutputStream out = new DataOutputStream(outputStream);
out.writeBytes("--" + boundary + CRLF);
out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"" + CRLF);
out.writeBytes("Content-Type: image/png" + CRLF);
out.writeBytes(CRLF);
out.write(Files.readAllBytes(file.toPath()));
out.writeBytes(CRLF);
out.writeBytes("--" + boundary + "--" + CRLF);
out.flush();
out.close();
outputStream.close();
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
if (obj.has("error")) return null;
if (!obj.has("data")) return null;
JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString());
}
} catch (IOException exception) {
if (!configManager.getConfig().disableSkinFetcherWarnings()) {
logger.warning("Failed to get skin from file:");
exception.printStackTrace();
}
} finally {
if (connection != null) connection.disconnect();
}
return null;
});
}
public boolean isNameFullyCached(String s) {
String name = s.toLowerCase();
if (!idCache.containsKey(name)) return false;
@ -170,7 +227,7 @@ public class MojangSkinCache {
if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
}
return CompletableFuture.supplyAsync(() -> {
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
HttpURLConnection connection = null;
try {
@ -213,4 +270,8 @@ public class MojangSkinCache {
throw new RuntimeException(exception);
}
}
public File getSkinsFolder() {
return skinsFolder;
}
}

View file

@ -5,8 +5,10 @@ import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.entity.Player;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
@ -18,11 +20,21 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
}
public static CompletableFuture<PrefetchedDescriptor> forPlayer(MojangSkinCache cache, String name) {
return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join()));
return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join()));
}
public static CompletableFuture<PrefetchedDescriptor> fromUrl(MojangSkinCache cache, URL url, String variant) {
return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
}
public static CompletableFuture<PrefetchedDescriptor> fromFile(MojangSkinCache cache, String path) {
return CompletableFuture.supplyAsync(() -> {
try {
return new PrefetchedDescriptor(cache.fetchFromFile(path).join());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
});
}
@Override

View file

@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.mysql.MySQLStorage;
import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage;
import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
@ -16,13 +17,13 @@ import java.io.File;
public enum NpcStorageType {
YAML {
@Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
return new YamlStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "data"));
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
return new YamlStorage(serializerRegistry, new File(plugin.getDataFolder(), "data"));
}
},
SQLITE {
@Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
try {
return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite"));
} catch (Exception e) {
@ -33,7 +34,7 @@ public enum NpcStorageType {
},
MYSQL {
@Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
try {
return new MySQLStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
} catch (Exception e) {
@ -43,5 +44,5 @@ public enum NpcStorageType {
}
};
public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer);
public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry);
}

View file

@ -1,47 +1,28 @@
package lol.pyr.znpcsplus.storage.yaml;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PropertySerializer;
import lol.pyr.znpcsplus.hologram.HologramImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.*;
import java.util.logging.Level;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class YamlStorage implements NpcStorage {
private final static Logger logger = Logger.getLogger("YamlStorage");
private final PacketFactory packetFactory;
private final ConfigManager configManager;
private final ActionRegistryImpl actionRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final LegacyComponentSerializer textSerializer;
private final File folder;
private final NpcSerializer<YamlConfiguration> yamlSerializer;
public YamlStorage(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, File folder) {
this.packetFactory = packetFactory;
this.configManager = configManager;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
public YamlStorage(NpcSerializerRegistryImpl serializerRegistry, File folder) {
this.yamlSerializer = serializerRegistry.getSerializer(YamlConfiguration.class);
this.folder = folder;
if (!this.folder.exists()) this.folder.mkdirs();
}
@ -53,45 +34,7 @@ public class YamlStorage implements NpcStorage {
List<NpcEntryImpl> npcs = new ArrayList<>(files.length);
for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) try {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID();
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, config.getString("world"),
typeRegistry.getByName(config.getString("type")), deserializeLocation(config.getConfigurationSection("location")));
if (config.isBoolean("enabled")) npc.setEnabled(config.getBoolean("enabled"));
ConfigurationSection properties = config.getConfigurationSection("properties");
if (properties != null) {
for (String key : properties.getKeys(false)) {
EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
if (property == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
Object value = serializer.deserialize(properties.getString(key));
if (value == null) {
Bukkit.getLogger().log(Level.WARNING, "Failed to deserialize property '" + key + "' for npc '" + config.getString("id") + "'. Resetting to default ...");
value = property.getDefaultValue();
}
npc.UNSAFE_setProperty(property, value);
}
}
HologramImpl hologram = npc.getHologram();
hologram.setOffset(config.getDouble("hologram.offset", 0.0));
hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1));
for (String line : config.getStringList("hologram.lines")) hologram.addLine(line);
for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s));
NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
entry.setProcessed(config.getBoolean("is-processed"));
entry.setAllowCommandModification(config.getBoolean("allow-commands"));
entry.setSave(true);
npcs.add(entry);
npcs.add((NpcEntryImpl) yamlSerializer.deserialize(config));
} catch (Throwable t) {
logger.severe("Failed to load npc file: " + file.getName());
t.printStackTrace();
@ -102,43 +45,7 @@ public class YamlStorage implements NpcStorage {
@Override
public void saveNpcs(Collection<NpcEntryImpl> npcs) {
for (NpcEntryImpl entry : npcs) try {
YamlConfiguration config = new YamlConfiguration();
config.set("id", entry.getId());
config.set("is-processed", entry.isProcessed());
config.set("allow-commands", entry.isAllowCommandModification());
NpcImpl npc = entry.getNpc();
config.set("enabled", npc.isEnabled());
config.set("uuid", npc.getUuid().toString());
config.set("world", npc.getWorldName());
config.set("location", serializeLocation(npc.getLocation()));
config.set("type", npc.getType().getName());
for (EntityProperty<?> property : npc.getAllProperties()) try {
PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
continue;
}
config.set("properties." + property.getName(), serializer.UNSAFE_serialize(npc.getProperty(property)));
} catch (Exception exception) {
logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
exception.printStackTrace();
}
HologramImpl hologram = npc.getHologram();
if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset());
if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay());
List<String> lines = new ArrayList<>(npc.getHologram().getLines().size());
for (int i = 0; i < hologram.getLines().size(); i++) {
lines.add(hologram.getLine(i));
}
config.set("hologram.lines", lines);
config.set("actions", npc.getActions().stream()
.map(actionRegistry::serialize)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
YamlConfiguration config = yamlSerializer.serialize(entry);
config.save(fileFor(entry));
} catch (Exception exception) {
logger.severe("Failed to save npc with id " + entry.getId());

View file

@ -33,6 +33,7 @@ public class NpcProcessorTask extends BukkitRunnable {
EntityPropertyImpl<Integer> viewDistanceProperty = propertyRegistry.getByName("view_distance", Integer.class); // Not sure why this is an Integer, but it is
EntityPropertyImpl<LookType> lookProperty = propertyRegistry.getByName("look", LookType.class);
EntityPropertyImpl<Double> lookDistanceProperty = propertyRegistry.getByName("look_distance", Double.class);
EntityPropertyImpl<Boolean> lookReturnProperty = propertyRegistry.getByName("look_return", Boolean.class);
EntityPropertyImpl<Boolean> permissionRequiredProperty = propertyRegistry.getByName("permission_required", Boolean.class);
EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class);
EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class);
@ -45,6 +46,7 @@ public class NpcProcessorTask extends BukkitRunnable {
EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class);
EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class);
double lookDistance;
boolean lookReturn;
boolean permissionRequired;
boolean playerKnockback;
String playerKnockbackExemptPermission = null;
@ -64,6 +66,7 @@ public class NpcProcessorTask extends BukkitRunnable {
Player closest = null;
LookType lookType = npc.getProperty(lookProperty);
lookDistance = NumberConversions.square(npc.getProperty(lookDistanceProperty));
lookReturn = npc.getProperty(lookReturnProperty);
permissionRequired = npc.getProperty(permissionRequiredProperty);
playerKnockback = npc.getProperty(playerKnockbackProperty);
if (playerKnockback) {
@ -106,9 +109,13 @@ public class NpcProcessorTask extends BukkitRunnable {
closestDist = distance;
closest = player;
}
if (lookType.equals(LookType.PER_PLAYER) && lookDistance >= distance) {
NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
if (lookType.equals(LookType.PER_PLAYER)) {
if (lookDistance >= distance) {
NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
} else if (lookReturn) {
npc.setHeadRotation(player, npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
}
// player knockback
@ -132,7 +139,11 @@ public class NpcProcessorTask extends BukkitRunnable {
if (closest != null && lookDistance >= closestDist) {
NpcLocation expected = npc.getLocation().lookingAt(closest.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(expected.getYaw(), expected.getPitch());
} else if (lookReturn) {
npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
} else if (lookType.equals(LookType.FIXED)) {
npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
}
}

View file

@ -0,0 +1,34 @@
package lol.pyr.znpcsplus.util;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public class FutureUtil {
public static CompletableFuture<Void> allOf(Collection<CompletableFuture<?>> futures) {
return exceptionPrintingRunAsync(() -> {
for (CompletableFuture<?> future : futures) future.join();
});
}
public static <T> CompletableFuture<T> newExceptionPrintingFuture() {
return new CompletableFuture<T>().exceptionally(throwable -> {
throwable.printStackTrace();
return null;
});
}
public static CompletableFuture<Void> exceptionPrintingRunAsync(Runnable runnable) {
return CompletableFuture.runAsync(runnable).exceptionally(throwable -> {
throwable.printStackTrace();
return null;
});
}
public static <T> CompletableFuture<T> exceptionPrintingSupplyAsync(Supplier<T> supplier) {
return CompletableFuture.supplyAsync(supplier).exceptionally(throwable -> {
throwable.printStackTrace();
return null;
});
}
}

View file

@ -4,11 +4,10 @@ import org.bukkit.entity.Player;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
public abstract class Viewable {
@ -21,38 +20,74 @@ public abstract class Viewable {
.collect(Collectors.toList());
}
private boolean queueRunning = false;
private final Queue<Runnable> visibilityTaskQueue = new ConcurrentLinkedQueue<>();
private final Set<Player> viewers = ConcurrentHashMap.newKeySet();
public Viewable() {
all.add(new WeakReference<>(this));
}
private void tryRunQueue() {
if (visibilityTaskQueue.isEmpty() || queueRunning) return;
queueRunning = true;
FutureUtil.exceptionPrintingRunAsync(() -> {
while (!visibilityTaskQueue.isEmpty()) try {
visibilityTaskQueue.remove().run();
} catch (Exception e) {
e.printStackTrace();
}
queueRunning = false;
});
}
private void queueVisibilityTask(Runnable runnable) {
visibilityTaskQueue.add(runnable);
tryRunQueue();
}
public void delete() {
UNSAFE_hideAll();
viewers.clear();
queueVisibilityTask(() -> {
UNSAFE_hideAll();
viewers.clear();
});
}
public void respawn() {
UNSAFE_hideAll();
UNSAFE_showAll();
public CompletableFuture<Void> respawn() {
CompletableFuture<Void> future = new CompletableFuture<>();
queueVisibilityTask(() -> {
UNSAFE_hideAll();
UNSAFE_showAll().join();
future.complete(null);
});
return future;
}
public void respawn(Player player) {
if (!viewers.contains(player)) return;
UNSAFE_hide(player);
UNSAFE_show(player);
public CompletableFuture<Void> respawn(Player player) {
hide(player);
return show(player);
}
public void show(Player player) {
if (viewers.contains(player)) return;
viewers.add(player);
UNSAFE_show(player);
public CompletableFuture<Void> show(Player player) {
CompletableFuture<Void> future = new CompletableFuture<>();
queueVisibilityTask(() -> {
if (viewers.contains(player)) {
future.complete(null);
return;
}
viewers.add(player);
UNSAFE_show(player).join();
future.complete(null);
});
return future;
}
public void hide(Player player) {
if (!viewers.contains(player)) return;
viewers.remove(player);
UNSAFE_hide(player);
queueVisibilityTask(() -> {
if (!viewers.contains(player)) return;
viewers.remove(player);
UNSAFE_hide(player);
});
}
public void UNSAFE_removeViewer(Player player) {
@ -63,8 +98,10 @@ public abstract class Viewable {
for (Player viewer : viewers) UNSAFE_hide(viewer);
}
protected void UNSAFE_showAll() {
for (Player viewer : viewers) UNSAFE_show(viewer);
protected CompletableFuture<Void> UNSAFE_showAll() {
return FutureUtil.allOf(viewers.stream()
.map(this::UNSAFE_show)
.collect(Collectors.toList()));
}
public Set<Player> getViewers() {
@ -75,7 +112,7 @@ public abstract class Viewable {
return viewers.contains(player);
}
protected abstract void UNSAFE_show(Player player);
protected abstract CompletableFuture<Void> UNSAFE_show(Player player);
protected abstract void UNSAFE_hide(Player player);
}

View file

@ -11,6 +11,9 @@ api-version: 1.13
folia-supported: true
depend:
- packetevents
softdepend:
- PlaceholderAPI
- ServersNPC