Compare commits

..

No commits in common. "4a965def75e3f53421b64af8215f08c612a89d1d" and "ae1dd25a9a6497f606ed03ad5f0df0b9f452a8c2" have entirely different histories.

54 changed files with 141 additions and 452 deletions

@ -12,14 +12,14 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.
## Why is it so good? ## Why is it so good?
- 100% Packet Based - Nothing is ran on the main thread - 100% Packet Based - Nothing is ran on the main thread
- Performance & stability oriented code - Performance & stability oriented code
- Support for all versions from 1.8 to 1.20.4 - Support for all versions from 1.8 to 1.20.2
- Support for multiple different storage options - Support for multiple different storage options
- Intuitive command system - Intuitive command system
### Requirements, Extensions & Supported Software ### Requirements, Extensions & Supported Software
Requirements: Requirements:
- Java 8+ - Java 8+
- Minecraft 1.8 - 1.20.4 - Minecraft 1.8 - 1.20.2
Supported Softwares: Supported Softwares:
- Spigot ([Website](https://www.spigotmc.org/)) - Spigot ([Website](https://www.spigotmc.org/))

@ -1,7 +1,5 @@
package lol.pyr.znpcsplus.api.entity; package lol.pyr.znpcsplus.api.entity;
import org.bukkit.inventory.ItemStack;
import java.util.Set; import java.util.Set;
/** /**
@ -34,19 +32,10 @@ public interface PropertyHolder {
*/ */
<T> void setProperty(EntityProperty<T> key, T value); <T> void setProperty(EntityProperty<T> key, T value);
/**
* Weird fix which is sadly required in order to not decrease performance
* when using item properties, read https://github.com/Pyrbu/ZNPCsPlus/pull/129#issuecomment-1948777764
*
* @param key Unique key representing a property
* @param value The value to assign to the property key on this holder
*/
void setItemProperty(EntityProperty<?> key, ItemStack value);
/** /**
* Method used to get a set of all of the property keys that this holder has a value for * Method used to get a set of all of the property keys that this holder has a value for
* *
* @return Set of property keys * @return List of property keys
*/ */
Set<EntityProperty<?>> getAppliedProperties(); Set<EntityProperty<?>> getAppliedProperties();
} }

@ -124,10 +124,4 @@ public interface Npc extends PropertyHolder {
* @return The set of players that can currently see this npc * @return The set of players that can currently see this npc
*/ */
Set<Player> getViewers(); Set<Player> getViewers();
/**
* Swings the entity's hand
* @param offHand Should the hand be the offhand
*/
void swingHand(boolean offHand);
} }

@ -1,6 +0,0 @@
package lol.pyr.znpcsplus.api.skin;
public interface Skin {
String getTexture();
String getSignature();
}

@ -1,11 +1,4 @@
package lol.pyr.znpcsplus.api.skin; package lol.pyr.znpcsplus.api.skin;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public interface SkinDescriptor { public interface SkinDescriptor {
CompletableFuture<? extends Skin> fetch(Player player);
Skin fetchInstant(Player player);
boolean supportsInstant(Player player);
} }

@ -33,6 +33,9 @@ subprojects {
maven { maven {
url "https://repo.pyr.lol/releases" url "https://repo.pyr.lol/releases"
} }
maven {
url "https://jitpack.io/"
}
} }
publishing { publishing {

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

@ -99,6 +99,16 @@ public class ZNpcsPlus {
PluginManager pluginManager = Bukkit.getPluginManager(); PluginManager pluginManager = Bukkit.getPluginManager();
long before = System.currentTimeMillis(); long before = System.currentTimeMillis();
boolean legacy = new File(getDataFolder(), "data.json").isFile() && !new File(getDataFolder(), "data").isDirectory();
if (legacy) try {
Files.move(getDataFolder().toPath(), new File(getDataFolder().getParentFile(), "ZNPCsPlusLegacy").toPath());
} catch (IOException e) {
log(ChatColor.RED + " * Moving legacy files to subfolder failed, plugin will shut down.");
e.printStackTrace();
pluginManager.disablePlugin(bootstrap);
return;
}
log(ChatColor.WHITE + " * Initializing libraries..."); log(ChatColor.WHITE + " * Initializing libraries...");
packetEvents.init(); packetEvents.init();
@ -149,6 +159,7 @@ public class ZNpcsPlus {
if (configManager.getConfig().checkForUpdates()) { if (configManager.getConfig().checkForUpdates()) {
UpdateChecker updateChecker = new UpdateChecker(getDescription()); UpdateChecker updateChecker = new UpdateChecker(getDescription());
scheduler.runDelayedTimerAsync(updateChecker, 5L, 6000L); scheduler.runDelayedTimerAsync(updateChecker, 5L, 6000L);
shutdownTasks.add(updateChecker::cancel);
pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker, scheduler), bootstrap); pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker, scheduler), bootstrap);
} }
@ -161,7 +172,7 @@ public class ZNpcsPlus {
npcRegistry.reload(); npcRegistry.reload();
if (configManager.getConfig().autoSaveEnabled()) shutdownTasks.add(npcRegistry::save); if (configManager.getConfig().autoSaveEnabled()) shutdownTasks.add(npcRegistry::save);
if (bootstrap.movedLegacy()) { if (legacy) {
log(ChatColor.WHITE + " * Converting legacy data..."); log(ChatColor.WHITE + " * Converting legacy data...");
try { try {
Collection<NpcEntryImpl> entries = importerRegistry.getImporter("znpcsplus_legacy").importData(); Collection<NpcEntryImpl> entries = importerRegistry.getImporter("znpcsplus_legacy").importData();
@ -199,12 +210,7 @@ public class ZNpcsPlus {
public void onDisable() { public void onDisable() {
NpcApiProvider.unregister(); NpcApiProvider.unregister();
for (Runnable runnable : shutdownTasks) try { for (Runnable runnable : shutdownTasks) runnable.run();
runnable.run();
} catch (Throwable throwable) {
bootstrap.getLogger().severe("One of the registered shutdown tasks threw an exception:");
throwable.printStackTrace();
}
shutdownTasks.clear(); shutdownTasks.clear();
PacketEvents.getAPI().terminate(); PacketEvents.getAPI().terminate();
} }
@ -213,7 +219,7 @@ public class ZNpcsPlus {
HashMap<ServerVersion, LazyLoader<? extends PacketFactory>> versions = new HashMap<>(); HashMap<ServerVersion, LazyLoader<? extends PacketFactory>> versions = new HashMap<>();
versions.put(ServerVersion.V_1_8, LazyLoader.of(() -> new V1_8PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager))); versions.put(ServerVersion.V_1_8, LazyLoader.of(() -> new V1_8PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_17, LazyLoader.of(() -> new V1_17PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager))); versions.put(ServerVersion.V_1_17, LazyLoader.of(() -> new V1_17PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_19_3, LazyLoader.of(() -> new V1_19_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager))); versions.put(ServerVersion.V_1_19_2, LazyLoader.of(() -> new V1_19_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager))); versions.put(ServerVersion.V_1_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
ServerVersion version = packetEvents.getServerManager().getVersion(); ServerVersion version = packetEvents.getServerManager().getVersion();

@ -6,32 +6,18 @@ import lol.pyr.znpcsplus.libraries.LibraryLoader;
import lol.pyr.znpcsplus.util.FileUtil; import lol.pyr.znpcsplus.util.FileUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.nio.file.Files;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class ZNpcsPlusBootstrap extends JavaPlugin { public class ZNpcsPlusBootstrap extends JavaPlugin {
private ZNpcsPlus zNpcsPlus; private ZNpcsPlus zNpcsPlus;
private boolean legacy;
@Override @Override
public void onLoad() { public void onLoad() {
legacy = new File(getDataFolder(), "data.json").isFile() && !new File(getDataFolder(), "data").isDirectory();
if (legacy) try {
Files.move(getDataFolder().toPath(), new File(getDataFolder().getParentFile(), "ZNPCsPlusLegacy").toPath());
} catch (IOException e) {
getLogger().severe(ChatColor.RED + "Failed to move legacy data folder! Plugin will disable.");
e.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return;
}
getLogger().info("Downloading and loading libraries, this might take a while if this is the first time you're launching the plugin"); getLogger().info("Downloading and loading libraries, this might take a while if this is the first time you're launching the plugin");
LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries")); LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries"));
@ -52,10 +38,10 @@ public class ZNpcsPlusBootstrap extends JavaPlugin {
loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2"); loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2");
loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2"); loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2");
loader.loadLibrary("me.robertlit", "SpigotResourcesAPI", "2.0", "https://repo.pyr.lol/releases"); loader.loadLibrary("com.github.robertlit", "SpigotResourcesAPI", "2.0", "https://jitpack.io");
loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "api", "2.2.1", "https://repo.codemc.io/repository/maven-releases/"); loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "api", "2.2.0", "https://repo.codemc.io/repository/maven-releases/");
loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "spigot", "2.2.1", "https://repo.codemc.io/repository/maven-releases/"); loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "spigot", "2.2.0", "https://repo.codemc.io/repository/maven-releases/");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1"); loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1");
loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1"); loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1");
@ -63,19 +49,19 @@ public class ZNpcsPlusBootstrap extends JavaPlugin {
loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases"); loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases");
loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.2"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.2"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.2"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.2"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.1");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson-legacy-impl", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson-legacy-impl", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json-legacy-impl", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json-legacy-impl", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.14.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0"); loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0");
loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0"); loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0");
loader.deleteUnloadedLibraries(); loader.deleteUnloadedLibraries();
@ -117,10 +103,6 @@ public class ZNpcsPlusBootstrap extends JavaPlugin {
return context -> context.send(component); return context -> context.send(component);
} }
public boolean movedLegacy() {
return legacy;
}
// Ugly hack because of https://github.com/johnrengelman/shadow/issues/232 // Ugly hack because of https://github.com/johnrengelman/shadow/issues/232
private static String decrypt(String packageName) { private static String decrypt(String packageName) {
return packageName.replace("..", "."); return packageName.replace("..", ".");

@ -10,7 +10,6 @@ import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -25,9 +24,9 @@ public class SetLocationCommand implements CommandHandler {
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " setlocation <id> <x> <y> <z>"); context.setUsage(context.getLabel() + " setlocation <id> <x> <y> <z>");
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
double x = parseLocation(context.popString(), npc.getLocation().getX()); double x = context.parse(Double.class);
double y = parseLocation(context.popString(), npc.getLocation().getY()); double y = context.parse(Double.class);
double z = parseLocation(context.popString(), npc.getLocation().getZ()); double z = context.parse(Double.class);
npc.setLocation(new NpcLocation(x, y, z, npc.getLocation().getYaw(), npc.getLocation().getPitch())); npc.setLocation(new NpcLocation(x, y, z, npc.getLocation().getYaw(), npc.getLocation().getPitch()));
context.send(Component.text("NPC has been moved to " + x + ", " + y + ", " + z + ".", NamedTextColor.GREEN)); context.send(Component.text("NPC has been moved to " + x + ", " + y + ", " + z + ".", NamedTextColor.GREEN));
} }
@ -35,22 +34,6 @@ public class SetLocationCommand implements CommandHandler {
@Override @Override
public List<String> suggest(CommandContext context) throws CommandExecutionException { public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
NpcImpl npc = context.suggestionParse(0, NpcEntryImpl.class).getNpc();
if (context.argSize() == 2) return Arrays.asList(String.valueOf(npc.getLocation().getX()), "~");
else if (context.argSize() == 3) return Arrays.asList(String.valueOf(npc.getLocation().getY()), "~");
else if (context.argSize() == 4) return Arrays.asList(String.valueOf(npc.getLocation().getZ()), "~");
return Collections.emptyList(); return Collections.emptyList();
} }
private static double parseLocation(String input, double current) throws CommandExecutionException {
if (input.equals("~")) return current;
if (input.startsWith("~")) {
try {
return current + Double.parseDouble(input.substring(1));
} catch (NumberFormatException e) {
throw new CommandExecutionException();
}
}
return Double.parseDouble(input);
}
} }

@ -10,7 +10,6 @@ import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -25,12 +24,8 @@ public class SetRotationCommand implements CommandHandler {
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " setrotation <id> <yaw> <pitch>"); context.setUsage(context.getLabel() + " setrotation <id> <yaw> <pitch>");
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
float yaw = parseRotation(context.popString(), npc.getLocation().getYaw()); float yaw = context.parse(Float.class);
float pitch = parseRotation(context.popString(), npc.getLocation().getPitch()); float pitch = context.parse(Float.class);
if (pitch < -90 || pitch > 90) {
pitch = Math.min(Math.max(pitch, -90), 90);
context.send(Component.text("Warning: pitch is outside of the -90 to 90 range. It has been normalized to " + pitch + ".", NamedTextColor.YELLOW));
}
npc.setLocation(new NpcLocation(npc.getLocation().getX(), npc.getLocation().getY(), npc.getLocation().getZ(), yaw, pitch)); npc.setLocation(new NpcLocation(npc.getLocation().getX(), npc.getLocation().getY(), npc.getLocation().getZ(), yaw, pitch));
context.send(Component.text("NPC has been rotated to " + yaw + ", " + pitch + ".", NamedTextColor.GREEN)); context.send(Component.text("NPC has been rotated to " + yaw + ", " + pitch + ".", NamedTextColor.GREEN));
} }
@ -38,21 +33,6 @@ public class SetRotationCommand implements CommandHandler {
@Override @Override
public List<String> suggest(CommandContext context) throws CommandExecutionException { public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
NpcImpl npc = context.suggestionParse(0, NpcEntryImpl.class).getNpc();
if (context.argSize() == 2) return Arrays.asList(String.valueOf(npc.getLocation().getYaw()), "~");
else if (context.argSize() == 3) return Arrays.asList(String.valueOf(npc.getLocation().getPitch()), "~");
return Collections.emptyList(); return Collections.emptyList();
} }
private static float parseRotation(String input, float current) throws CommandExecutionException {
if (input.equals("~")) return current;
if (input.startsWith("~")) {
try {
return current + Float.parseFloat(input.substring(1));
} catch (NumberFormatException e) {
throw new CommandExecutionException();
}
}
return Float.parseFloat(input);
}
} }

@ -68,9 +68,4 @@ public interface MainConfig {
}) })
@DefaultInteger(60) @DefaultInteger(60)
int tabHideDelay(); int tabHideDelay();
@ConfKey("tab-display-name")
@ConfComments("The display name to use for npcs in the player list (aka tab)")
@DefaultString("ZNPC[{id}]")
String tabDisplayName();
} }

@ -4,7 +4,7 @@ import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait;
import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -21,7 +21,7 @@ public class SkinTrait extends SectionCitizensTrait {
public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) {
String texture = section.getString("textureRaw"); String texture = section.getString("textureRaw");
String signature = section.getString("signature"); String signature = section.getString("signature");
if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(texture, signature))); if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(texture, signature)));
return npc; return npc;
} }
} }

@ -8,7 +8,9 @@ import lol.pyr.znpcsplus.api.interaction.InteractionType;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.DataImporter; import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.conversion.znpcs.model.*; import lol.pyr.znpcsplus.conversion.znpcs.model.ZNpcsAction;
import lol.pyr.znpcsplus.conversion.znpcs.model.ZNpcsLocation;
import lol.pyr.znpcsplus.conversion.znpcs.model.ZNpcsModel;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.hologram.HologramImpl; import lol.pyr.znpcsplus.hologram.HologramImpl;
@ -23,7 +25,7 @@ import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
@ -34,6 +36,7 @@ import lol.pyr.znpcsplus.util.LookType;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.DyeColor; import org.bukkit.DyeColor;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -54,7 +57,6 @@ public class ZNpcImporter implements DataImporter {
private final EntityPropertyRegistryImpl propertyRegistry; private final EntityPropertyRegistryImpl propertyRegistry;
private final MojangSkinCache skinCache; private final MojangSkinCache skinCache;
private final File dataFile; private final File dataFile;
private final File conversationFile;
private final Gson gson; private final Gson gson;
private final BungeeConnector bungeeConnector; private final BungeeConnector bungeeConnector;
@ -71,7 +73,6 @@ public class ZNpcImporter implements DataImporter {
this.propertyRegistry = propertyRegistry; this.propertyRegistry = propertyRegistry;
this.skinCache = skinCache; this.skinCache = skinCache;
this.dataFile = dataFile; this.dataFile = dataFile;
this.conversationFile = new File(dataFile.getParentFile(), "conversations.json");
this.bungeeConnector = bungeeConnector; this.bungeeConnector = bungeeConnector;
gson = new GsonBuilder() gson = new GsonBuilder()
.create(); .create();
@ -88,19 +89,6 @@ public class ZNpcImporter implements DataImporter {
return Collections.emptyList(); return Collections.emptyList();
} }
if (models == null) return Collections.emptyList(); if (models == null) return Collections.emptyList();
ZnpcsConversations[] conversations;
try (BufferedReader fileReader = Files.newBufferedReader(conversationFile.toPath())) {
JsonElement element = JsonParser.parseReader(fileReader);
conversations = gson.fromJson(element, ZnpcsConversations[].class);
} catch (IOException e) {
e.printStackTrace();
return Collections.emptyList();
}
if (conversations == null) return Collections.emptyList();
ArrayList<NpcEntryImpl> entries = new ArrayList<>(models.length); ArrayList<NpcEntryImpl> entries = new ArrayList<>(models.length);
for (ZNpcsModel model : models) { for (ZNpcsModel model : models) {
String type = model.getNpcType(); String type = model.getNpcType();
@ -120,41 +108,6 @@ public class ZNpcImporter implements DataImporter {
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, oldLoc.getWorld(), typeRegistry.getByName(type), location); NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, oldLoc.getWorld(), typeRegistry.getByName(type), location);
npc.getType().applyDefaultProperties(npc); npc.getType().applyDefaultProperties(npc);
// Convert the conversations from each NPC
ZNpcsConversation conversation = model.getConversation();
if (conversation != null) {
// Loop through all conversations in the conversations.json file
for (ZnpcsConversations conv : conversations) {
// If the conversation name matches the conversation name in the data.json file, proceed
if (conv.getName().equalsIgnoreCase(conversation.getConversationName())) {
int totalDelay = 0;
// Loop through all texts in the conversation
for(ZNpcsConversationText text : conv.getTexts()) {
// Add the delay in ticks to the total delay
totalDelay += text.getDelay() * 20;
// Get the lines of text from the conversation
String[] lines = text.getLines();
// Loop through all lines of text
for (String line : lines) {
// Create a new message action for each line of text
InteractionActionImpl action = new MessageAction(adventure, line, InteractionType.ANY_CLICK, textSerializer, 0, totalDelay);
npc.addAction(action);
}
}
}
}
}
HologramImpl hologram = npc.getHologram(); HologramImpl hologram = npc.getHologram();
hologram.setOffset(model.getHologramHeight()); hologram.setOffset(model.getHologramHeight());
for (String raw : model.getHologramLines()) { for (String raw : model.getHologramLines()) {
@ -177,10 +130,10 @@ public class ZNpcImporter implements DataImporter {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName())); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName()));
} }
else if (model.getSkin() != null && model.getSignature() != null) { else if (model.getSkin() != null && model.getSignature() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature()))); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(model.getSkin(), model.getSignature())));
} }
Map<String, Object> toggleValues = model.getNpcToggleValues() == null ? model.getNpcFunctions() : model.getNpcToggleValues(); Map<String, Object> toggleValues = model.getNpcToggleValues();
if (toggleValues != null) { if (toggleValues != null) {
if (toggleValues.containsKey("look")) { if (toggleValues.containsKey("look")) {
npc.setProperty(propertyRegistry.getByName("look", LookType.class), LookType.CLOSEST_PLAYER); npc.setProperty(propertyRegistry.getByName("look", LookType.class), LookType.CLOSEST_PLAYER);

@ -1,16 +0,0 @@
package lol.pyr.znpcsplus.conversion.znpcs.model;
@SuppressWarnings("unused")
public class ZNpcsConversation {
private String conversationName;
private String conversationType;
public String getConversationName() {
return conversationName;
}
public String getConversationType() {
return conversationType;
}
}

@ -1,19 +0,0 @@
package lol.pyr.znpcsplus.conversion.znpcs.model;
@SuppressWarnings("unused")
public class ZNpcsConversationText {
private String[] lines;
private ZNpcsAction[] actions;
private int delay;
public String[] getLines() {
return lines;
}
public ZNpcsAction[] getActions() {
return actions;
}
public int getDelay() {
return delay;
}
}

@ -14,15 +14,12 @@ public class ZNpcsModel {
private String signature; private String signature;
private String glowName; private String glowName;
private ZNpcsConversation conversation;
private ZNpcsLocation location; private ZNpcsLocation location;
private String npcType; private String npcType;
private List<String> hologramLines; private List<String> hologramLines;
private List<ZNpcsAction> clickActions; private List<ZNpcsAction> clickActions;
private Map<String, String> npcEquip; private Map<String, String> npcEquip;
private Map<String, Object> npcToggleValues; private Map<String, Object> npcToggleValues;
private Map<String, Object> npcFunctions;
private Map<String, String[]> customizationMap; private Map<String, String[]> customizationMap;
public int getId() { public int getId() {
@ -41,10 +38,6 @@ public class ZNpcsModel {
return skinName; return skinName;
} }
public ZNpcsConversation getConversation() {
return conversation;
}
public ZNpcsLocation getLocation() { public ZNpcsLocation getLocation() {
return location; return location;
} }
@ -69,10 +62,6 @@ public class ZNpcsModel {
return npcToggleValues; return npcToggleValues;
} }
public Map<String, Object> getNpcFunctions() {
return npcFunctions;
}
public Map<String, String[]> getCustomizationMap() { public Map<String, String[]> getCustomizationMap() {
return customizationMap; return customizationMap;
} }

@ -1,23 +0,0 @@
package lol.pyr.znpcsplus.conversion.znpcs.model;
@SuppressWarnings("unused")
public class ZnpcsConversations {
private String name;
private ZNpcsConversationText[] texts;
private int radius;
private int delay;
public String getName() {
return name;
}
public ZNpcsConversationText[] getTexts() {
return texts;
}
public int getRadius() {
return radius;
}
public int getDelay() {
return delay;
}
}

@ -92,10 +92,18 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
/* /*
registerType("using_item", false); // TODO: fix it for 1.8 and add new property to use offhand item and riptide animation registerType("using_item", false); // TODO: fix it for 1.8 and add new property to use offhand item and riptide animation
// End Crystal
registerType("beam_target", null); // TODO: Make a block pos class for this
registerType("show_base", true); // TODO
// Enderman // Enderman
registerType("enderman_held_block", new BlockState(0)); // TODO: figure out the type on this registerType("enderman_held_block", new BlockState(0)); // TODO: figure out the type on this
registerType("enderman_screaming", false); // TODO registerType("enderman_screaming", false); // TODO
registerType("enderman_staring", false); // TODO registerType("enderman_staring", false); // TODO
// Guardian
registerType("is_elder", false); // TODO: ensure it only works till 1.10. Note: index is wrong on wiki.vg
*/ */
} }

@ -10,7 +10,6 @@ import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
@ -73,10 +72,6 @@ public class PacketEntity implements PropertyHolder {
packetFactory.sendAllMetadata(player, this, properties); packetFactory.sendAllMetadata(player, this, properties);
} }
public void swingHand(Player player, boolean offhand) {
packetFactory.sendHandSwing(player, this, offhand);
}
private static int reserveEntityID() { private static int reserveEntityID() {
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14)) { if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14)) {
return Reflections.ATOMIC_ENTITY_ID_FIELD.get().incrementAndGet(); return Reflections.ATOMIC_ENTITY_ID_FIELD.get().incrementAndGet();
@ -102,11 +97,6 @@ public class PacketEntity implements PropertyHolder {
properties.setProperty(key, value); properties.setProperty(key, value);
} }
@Override
public void setItemProperty(EntityProperty<?> key, ItemStack value) {
properties.setItemProperty(key, value);
}
@Override @Override
public Set<EntityProperty<?>> getAppliedProperties() { public Set<EntityProperty<?>> getAppliedProperties() {
return properties.getAppliedProperties(); return properties.getAppliedProperties();

@ -106,7 +106,7 @@ public class HologramImpl extends Viewable implements Hologram {
} }
public void insertTextLine(int index, String line) { public void insertTextLine(int index, String line) {
insertTextLineComponent(index, textSerializer.deserialize(textSerializer.serialize(MiniMessage.miniMessage().deserialize(line)))); insertTextLineComponent(index, textSerializer.deserialize(line));
} }
public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) { public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) {

@ -7,7 +7,6 @@ import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@ -71,11 +70,6 @@ public class HologramLine<M> implements PropertyHolder {
throw new UnsupportedOperationException("Can't set properties on a hologram line"); throw new UnsupportedOperationException("Can't set properties on a hologram line");
} }
@Override
public void setItemProperty(EntityProperty<?> key, ItemStack value) {
throw new UnsupportedOperationException("Can't set properties on a hologram line");
}
@Override @Override
public Set<EntityProperty<?>> getAppliedProperties() { public Set<EntityProperty<?>> getAppliedProperties() {
return properties; return properties;

@ -1,7 +1,6 @@
package lol.pyr.znpcsplus.npc; package lol.pyr.znpcsplus.npc;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.npc.Npc; import lol.pyr.znpcsplus.api.npc.Npc;
import lol.pyr.znpcsplus.api.npc.NpcType; import lol.pyr.znpcsplus.api.npc.NpcType;
@ -19,7 +18,6 @@ import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -148,20 +146,9 @@ public class NpcImpl extends Viewable implements Npc {
return propertyMap.containsKey((EntityPropertyImpl<?>) key); return propertyMap.containsKey((EntityPropertyImpl<?>) key);
} }
@SuppressWarnings("unchecked")
@Override @Override
public <T> void setProperty(EntityProperty<T> key, T value) { public <T> void setProperty(EntityProperty<T> key, T value) {
// See https://github.com/Pyrbu/ZNPCsPlus/pull/129#issuecomment-1948777764 setProperty((EntityPropertyImpl<T>) key, value );
Object val = value;
if (val instanceof ItemStack) val = SpigotConversionUtil.fromBukkitItemStack((ItemStack) val);
setProperty((EntityPropertyImpl<T>) key, (T) val);
}
@SuppressWarnings("unchecked")
@Override
public void setItemProperty(EntityProperty<?> key, ItemStack value) {
setProperty((EntityPropertyImpl<com.github.retrooper.packetevents.protocol.item.ItemStack>) key, SpigotConversionUtil.fromBukkitItemStack(value));
} }
public <T> void setProperty(EntityPropertyImpl<T> key, T value) { public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
@ -214,8 +201,4 @@ public class NpcImpl extends Viewable implements Npc {
delete(); delete();
this.worldName = world.getName(); this.worldName = world.getName();
} }
public void swingHand(boolean offHand) {
for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
}
} }

@ -156,7 +156,7 @@ public class NpcTypeImpl implements NpcType {
} }
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.PANDA)) { if (EntityTypes.isTypeInstanceOf(type, EntityTypes.PANDA)) {
if (version.isNewerThanOrEquals(ServerVersion.V_1_15)) { if (version.isNewerThanOrEquals(ServerVersion.V_1_15)) {
addProperties("panda_rolling", "panda_sitting", "panda_on_back", "hand"); addProperties("panda_rolling", "panda_sitting", "panda_on_back");
} else { } else {
addProperties("panda_eating"); addProperties("panda_eating");
} }

@ -23,5 +23,4 @@ public interface PacketFactory {
void sendEquipment(Player player, PacketEntity entity, Equipment equipment); void sendEquipment(Player player, PacketEntity entity, Equipment equipment);
void sendMetadata(Player player, PacketEntity entity, List<EntityData> data); void sendMetadata(Player player, PacketEntity entity, List<EntityData> data);
void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch); void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch);
void sendHandSwing(Player player, PacketEntity entity, boolean offHand);
} }

@ -19,8 +19,8 @@ import org.bukkit.plugin.Plugin;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class V1_19_3PacketFactory extends V1_17PacketFactory { public class V1_19_2PacketFactory extends V1_17PacketFactory {
public V1_19_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { public V1_19_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager); super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
} }
@ -30,8 +30,7 @@ public class V1_19_3PacketFactory extends V1_17PacketFactory {
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> { skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> {
WrapperPlayServerPlayerInfoUpdate.PlayerInfo info = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo( WrapperPlayServerPlayerInfoUpdate.PlayerInfo info = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo(
profile, false, 1, GameMode.CREATIVE, profile, false, 1, GameMode.CREATIVE, Component.empty(), null);
Component.text(configManager.getConfig().tabDisplayName().replace("{id}", Integer.toString(entity.getEntityId()))), null);
sendPacket(player, new WrapperPlayServerPlayerInfoUpdate(EnumSet.of(WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER, sendPacket(player, new WrapperPlayServerPlayerInfoUpdate(EnumSet.of(WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER,
WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_LISTED), info, info)); WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_LISTED), info, info));
future.complete(null); future.complete(null);

@ -17,13 +17,12 @@ import org.bukkit.plugin.Plugin;
import java.util.Optional; import java.util.Optional;
public class V1_20_2PacketFactory extends V1_19_3PacketFactory { public class V1_20_2PacketFactory extends V1_19_2PacketFactory {
protected ConfigManager configManager; protected ConfigManager configManager;
public V1_20_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { public V1_20_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager); super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
this.configManager = configManager;
} }
@Override @Override

@ -96,8 +96,7 @@ public class V1_8PacketFactory implements PacketFactory {
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> { skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> {
sendPacket(player, new WrapperPlayServerPlayerInfo( sendPacket(player, new WrapperPlayServerPlayerInfo(
WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData( WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(Component.text(" "),
Component.text(configManager.getConfig().tabDisplayName().replace("{id}", Integer.toString(entity.getEntityId()))),
profile, GameMode.CREATIVE, 1))); profile, GameMode.CREATIVE, 1)));
future.complete(null); future.complete(null);
}); });
@ -115,7 +114,7 @@ public class V1_8PacketFactory implements PacketFactory {
@Override @Override
public void createTeam(Player player, PacketEntity entity, NamedColor namedColor) { public void createTeam(Player player, PacketEntity entity, NamedColor namedColor) {
sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.CREATE, new WrapperPlayServerTeams.ScoreBoardTeamInfo( sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.CREATE, new WrapperPlayServerTeams.ScoreBoardTeamInfo(
Component.text(" "), null, null, Component.empty(), Component.empty(), Component.empty(),
WrapperPlayServerTeams.NameTagVisibility.NEVER, WrapperPlayServerTeams.NameTagVisibility.NEVER,
WrapperPlayServerTeams.CollisionRule.NEVER, WrapperPlayServerTeams.CollisionRule.NEVER,
namedColor == null ? NamedTextColor.WHITE : NamedTextColor.NAMES.value(namedColor.name().toLowerCase()), namedColor == null ? NamedTextColor.WHITE : NamedTextColor.NAMES.value(namedColor.name().toLowerCase()),
@ -175,11 +174,4 @@ public class V1_8PacketFactory implements PacketFactory {
protected void add(Map<Integer, EntityData> map, EntityData data) { protected void add(Map<Integer, EntityData> map, EntityData data) {
map.put(data.getIndex(), data); map.put(data.getIndex(), data);
} }
@Override
public void sendHandSwing(Player player, PacketEntity entity, boolean offHand) {
sendPacket(player, new WrapperPlayServerEntityAnimation(entity.getEntityId(), offHand ?
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND :
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM));
}
} }

@ -13,8 +13,8 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor extends SkinDescriptor { public interface BaseSkinDescriptor extends SkinDescriptor {
CompletableFuture<SkinImpl> fetch(Player player); CompletableFuture<Skin> fetch(Player player);
SkinImpl fetchInstant(Player player); Skin fetchInstant(Player player);
boolean supportsInstant(Player player); boolean supportsInstant(Player player);
String serialize(); String serialize();
@ -27,7 +27,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
for (int i = 0; i < (arr.length - 1) / 3; i++) { for (int i = 0; i < (arr.length - 1) / 3; i++) {
properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3])); properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3]));
} }
return new PrefetchedDescriptor(new SkinImpl(properties)); return new PrefetchedDescriptor(new Skin(properties));
} }
throw new IllegalArgumentException("Unknown SkinDescriptor type!"); throw new IllegalArgumentException("Unknown SkinDescriptor type!");
} }

@ -6,7 +6,6 @@ import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.github.retrooper.packetevents.protocol.player.UserProfile;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lol.pyr.znpcsplus.api.skin.Skin;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -14,21 +13,21 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class SkinImpl implements Skin { public class Skin {
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final List<TextureProperty> properties; private final List<TextureProperty> properties;
private static final boolean V1_20_2 = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2); private static final boolean V1_20_2 = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2);
public SkinImpl(String texture, String signature) { public Skin(String texture, String signature) {
properties = new ArrayList<>(1); properties = new ArrayList<>(1);
properties.add(new TextureProperty("textures", texture, signature)); properties.add(new TextureProperty("textures", texture, signature));
} }
public SkinImpl(Collection<TextureProperty> properties) { public Skin(Collection<TextureProperty> properties) {
this.properties = new ArrayList<>(properties); this.properties = new ArrayList<>(properties);
} }
public SkinImpl(Object propertyMap) { public Skin(Object propertyMap) {
this.properties = new ArrayList<>(); this.properties = new ArrayList<>();
try { try {
Collection<?> properties = (Collection<?>) Reflections.PROPERTY_MAP_VALUES_METHOD.get().invoke(propertyMap); Collection<?> properties = (Collection<?>) Reflections.PROPERTY_MAP_VALUES_METHOD.get().invoke(propertyMap);
@ -52,7 +51,7 @@ public class SkinImpl implements Skin {
} }
} }
public SkinImpl(JsonObject obj) { public Skin(JsonObject obj) {
properties = new ArrayList<>(); properties = new ArrayList<>();
for (JsonElement e : obj.get("properties").getAsJsonArray()) { for (JsonElement e : obj.get("properties").getAsJsonArray()) {
JsonObject o = e.getAsJsonObject(); JsonObject o = e.getAsJsonObject();
@ -72,20 +71,4 @@ public class SkinImpl implements Skin {
public boolean isExpired() { public boolean isExpired() {
return System.currentTimeMillis() - timestamp > 60000L; return System.currentTimeMillis() - timestamp > 60000L;
} }
@Override
public String getTexture() {
for (TextureProperty property : properties)
if (property.getName().equalsIgnoreCase("textures"))
return property.getValue();
return null;
}
@Override
public String getSignature() {
for (TextureProperty property : properties)
if (property.getName().equalsIgnoreCase("textures"))
return property.getSignature();
return null;
}
} }

@ -36,7 +36,7 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
@Override @Override
public SkinDescriptor createStaticDescriptor(String texture, String signature) { public SkinDescriptor createStaticDescriptor(String texture, String signature) {
return new PrefetchedDescriptor(new SkinImpl(texture, signature)); return new PrefetchedDescriptor(new Skin(texture, signature));
} }
@Override @Override

@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -24,7 +24,7 @@ public class MojangSkinCache {
private final ConfigManager configManager; private final ConfigManager configManager;
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>(); private final Map<String, Skin> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>(); private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
public MojangSkinCache(ConfigManager configManager) { public MojangSkinCache(ConfigManager configManager) {
@ -32,11 +32,11 @@ public class MojangSkinCache {
} }
public void cleanCache() { public void cleanCache() {
for (Map.Entry<String, SkinImpl> entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry<String, Skin> entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey());
for (Map.Entry<String, CachedId> entry : idCache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry<String, CachedId> entry : idCache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey());
} }
public CompletableFuture<SkinImpl> fetchByName(String name) { public CompletableFuture<Skin> fetchByName(String name) {
Player player = Bukkit.getPlayerExact(name); Player player = Bukkit.getPlayerExact(name);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
@ -53,7 +53,7 @@ public class MojangSkinCache {
if (obj.has("errorMessage")) return fetchByNameFallback(name).join(); if (obj.has("errorMessage")) return fetchByNameFallback(name).join();
String id = obj.get("id").getAsString(); String id = obj.get("id").getAsString();
idCache.put(name.toLowerCase(), new CachedId(id)); idCache.put(name.toLowerCase(), new CachedId(id));
SkinImpl skin = fetchByUUID(id).join(); Skin skin = fetchByUUID(id).join();
if (skin == null) return fetchByNameFallback(name).join(); if (skin == null) return fetchByNameFallback(name).join();
return skin; return skin;
} }
@ -69,7 +69,7 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<SkinImpl> fetchByNameFallback(String name) { public CompletableFuture<Skin> fetchByNameFallback(String name) {
Player player = Bukkit.getPlayerExact(name); Player player = Bukkit.getPlayerExact(name);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
@ -89,7 +89,7 @@ public class MojangSkinCache {
JsonObject textures = obj.get("textures").getAsJsonObject(); JsonObject textures = obj.get("textures").getAsJsonObject();
String value = textures.get("raw").getAsJsonObject().get("value").getAsString(); String value = textures.get("raw").getAsJsonObject().get("value").getAsString();
String signature = textures.get("raw").getAsJsonObject().get("signature").getAsString(); String signature = textures.get("raw").getAsJsonObject().get("signature").getAsString();
SkinImpl skin = new SkinImpl(value, signature); Skin skin = new Skin(value, signature);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -105,7 +105,7 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) { public CompletableFuture<Skin> fetchByUrl(URL url, String variant) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/url"); URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
HttpURLConnection connection = null; HttpURLConnection connection = null;
@ -127,7 +127,7 @@ public class MojangSkinCache {
if (obj.has("error")) return null; if (obj.has("error")) return null;
if (!obj.has("data")) return null; if (!obj.has("data")) return null;
JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject(); JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString()); return new Skin(texture.get("value").getAsString(), texture.get("signature").getAsString());
} }
} catch (IOException exception) { } catch (IOException exception) {
@ -147,26 +147,26 @@ public class MojangSkinCache {
if (!idCache.containsKey(name)) return false; if (!idCache.containsKey(name)) return false;
CachedId id = idCache.get(name); CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return false; if (id.isExpired() || !cache.containsKey(id.getId())) return false;
SkinImpl skin = cache.get(id.getId()); Skin skin = cache.get(id.getId());
return !skin.isExpired(); return !skin.isExpired();
} }
public SkinImpl getFullyCachedByName(String s) { public Skin getFullyCachedByName(String s) {
String name = s.toLowerCase(); String name = s.toLowerCase();
if (!idCache.containsKey(name)) return null; if (!idCache.containsKey(name)) return null;
CachedId id = idCache.get(name); CachedId id = idCache.get(name);
if (id.isExpired() || !cache.containsKey(id.getId())) return null; if (id.isExpired() || !cache.containsKey(id.getId())) return null;
SkinImpl skin = cache.get(id.getId()); Skin skin = cache.get(id.getId());
if (skin.isExpired()) return null; if (skin.isExpired()) return null;
return skin; return skin;
} }
public CompletableFuture<SkinImpl> fetchByUUID(String uuid) { public CompletableFuture<Skin> fetchByUUID(String uuid) {
Player player = Bukkit.getPlayer(uuid); Player player = Bukkit.getPlayer(uuid);
if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player));
if (cache.containsKey(uuid)) { if (cache.containsKey(uuid)) {
SkinImpl skin = cache.get(uuid); Skin skin = cache.get(uuid);
if (!skin.isExpired()) return CompletableFuture.completedFuture(skin); if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
} }
@ -179,7 +179,7 @@ public class MojangSkinCache {
try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) { try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject(); JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
if (obj.has("errorMessage")) return null; if (obj.has("errorMessage")) return null;
SkinImpl skin = new SkinImpl(obj); Skin skin = new Skin(obj);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -195,12 +195,12 @@ public class MojangSkinCache {
}); });
} }
public SkinImpl getFromPlayer(Player player) { public Skin getFromPlayer(Player player) {
try { try {
Object playerHandle = Reflections.GET_PLAYER_HANDLE_METHOD.get().invoke(player); Object playerHandle = Reflections.GET_PLAYER_HANDLE_METHOD.get().invoke(player);
Object gameProfile = Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle); Object gameProfile = Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle);
Object propertyMap = Reflections.GET_PROPERTY_MAP_METHOD.get().invoke(gameProfile); Object propertyMap = Reflections.GET_PROPERTY_MAP_METHOD.get().invoke(gameProfile);
return new SkinImpl(propertyMap); return new Skin(propertyMap);
} catch (IllegalAccessException | InvocationTargetException e) { } catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.PapiUtil; import lol.pyr.znpcsplus.util.PapiUtil;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -19,12 +19,12 @@ public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
} }
@Override @Override
public CompletableFuture<SkinImpl> fetch(Player player) { public CompletableFuture<Skin> fetch(Player player) {
return skinCache.fetchByName(PapiUtil.set(player, name)); return skinCache.fetchByName(PapiUtil.set(player, name));
} }
@Override @Override
public SkinImpl fetchInstant(Player player) { public Skin fetchInstant(Player player) {
return skinCache.getFullyCachedByName(PapiUtil.set(player, name)); return skinCache.getFullyCachedByName(PapiUtil.set(player, name));
} }

@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -16,12 +16,12 @@ public class MirrorDescriptor implements BaseSkinDescriptor, SkinDescriptor {
} }
@Override @Override
public CompletableFuture<SkinImpl> fetch(Player player) { public CompletableFuture<Skin> fetch(Player player) {
return CompletableFuture.completedFuture(skinCache.getFromPlayer(player)); return CompletableFuture.completedFuture(skinCache.getFromPlayer(player));
} }
@Override @Override
public SkinImpl fetchInstant(Player player) { public Skin fetchInstant(Player player) {
return skinCache.getFromPlayer(player); return skinCache.getFromPlayer(player);
} }

@ -3,7 +3,7 @@ package lol.pyr.znpcsplus.skin.descriptor;
import com.github.retrooper.packetevents.protocol.player.TextureProperty; import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.Skin;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -11,9 +11,9 @@ import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor { public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor {
private final SkinImpl skin; private final Skin skin;
public PrefetchedDescriptor(SkinImpl skin) { public PrefetchedDescriptor(Skin skin) {
this.skin = skin; this.skin = skin;
} }
@ -26,12 +26,12 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
} }
@Override @Override
public CompletableFuture<SkinImpl> fetch(Player player) { public CompletableFuture<Skin> fetch(Player player) {
return CompletableFuture.completedFuture(skin); return CompletableFuture.completedFuture(skin);
} }
@Override @Override
public SkinImpl fetchInstant(Player player) { public Skin fetchInstant(Player player) {
return skin; return skin;
} }
@ -40,7 +40,7 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
return true; return true;
} }
public SkinImpl getSkin() { public Skin getSkin() {
return skin; return skin;
} }

@ -28,7 +28,10 @@ public class UpdateChecker extends BukkitRunnable {
if (resource == null) return; if (resource == null) return;
newestVersion = resource.getVersion(); newestVersion = resource.getVersion();
status = compareVersions(info.getVersion(), newestVersion); int current = versionToNumber(info.getVersion());
int newest = versionToNumber(newestVersion);
status = current >= newest ? Status.LATEST_VERSION : Status.UPDATE_NEEDED;
if (status == Status.UPDATE_NEEDED) notifyConsole(); if (status == Status.UPDATE_NEEDED) notifyConsole();
} }
@ -37,43 +40,10 @@ public class UpdateChecker extends BukkitRunnable {
logger.warning("Download it at " + UpdateChecker.DOWNLOAD_LINK); logger.warning("Download it at " + UpdateChecker.DOWNLOAD_LINK);
} }
private Status compareVersions(String currentVersion, String newVersion) { private int versionToNumber(String version) {
if (currentVersion.equalsIgnoreCase(newVersion)) return Status.LATEST_VERSION; int num = Integer.parseInt(version.replaceAll("[^0-9]", ""));
ReleaseType currentType = parseReleaseType(currentVersion); if (version.toLowerCase().contains("snapshot")) num -= 1;
ReleaseType newType = parseReleaseType(newVersion); return num;
if (currentType == ReleaseType.UNKNOWN || newType == ReleaseType.UNKNOWN) return Status.UNKNOWN;
String currentVersionWithoutType = getVersionWithoutReleaseType(currentVersion);
String newVersionWithoutType = getVersionWithoutReleaseType(newVersion);
String[] currentParts = currentVersionWithoutType.split("\\.");
String[] newParts = newVersionWithoutType.split("\\.");
for (int i = 0; i < Math.min(currentParts.length, newParts.length); i++) {
int currentPart = Integer.parseInt(currentParts[i]);
int newPart = Integer.parseInt(newParts[i]);
if (newPart > currentPart) return Status.UPDATE_NEEDED;
}
if (newType.ordinal() > currentType.ordinal()) return Status.UPDATE_NEEDED;
if (newType == currentType) {
int currentReleaseTypeNumber = getReleaseTypeNumber(currentVersion);
int newReleaseTypeNumber = getReleaseTypeNumber(newVersion);
if (newReleaseTypeNumber > currentReleaseTypeNumber) return Status.UPDATE_NEEDED;
}
return Status.LATEST_VERSION;
}
private ReleaseType parseReleaseType(String version) {
if (version.toLowerCase().contains("snapshot")) return ReleaseType.SNAPSHOT;
if (version.toLowerCase().contains("alpha")) return ReleaseType.ALPHA;
if (version.toLowerCase().contains("beta")) return ReleaseType.BETA;
return version.matches("\\d+\\.\\d+\\.\\d+") ? ReleaseType.RELEASE : ReleaseType.UNKNOWN;
}
private String getVersionWithoutReleaseType(String version) {
return version.contains("-") ? version.split("-")[0] : version;
}
private int getReleaseTypeNumber(String version) {
if (!version.contains("-")) return 0;
return Integer.parseInt(version.split("-")[1].split("\\.")[1]);
} }
public Status getStatus() { public Status getStatus() {
@ -87,8 +57,4 @@ public class UpdateChecker extends BukkitRunnable {
public enum Status { public enum Status {
UNKNOWN, LATEST_VERSION, UPDATE_NEEDED UNKNOWN, LATEST_VERSION, UPDATE_NEEDED
} }
public enum ReleaseType {
UNKNOWN, SNAPSHOT, ALPHA, BETA, RELEASE
}
} }

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc property remove <gold><id> <property>
<gray>Command used to unset properties on npcs

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc property set <gold><id> <property> <value>
<gray>Command used to customize npcs with custom properties

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc center <gold><id>
<gray>Command used to move an npc to the center of the block it's currently occupying

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc changeid <gold><id> <new id>
<gray>Command used to change the id of an npc

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc create <gold><id> <type>
<gray>Command used to create an npc of a given type

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc delete <gold><id>
<gray>Command used to delete an npc

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc list
<gray>Command used to list all npcs

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc lookatme <gold><id>
<gray>Command used to set the rotation of an npc to be looking at your current location

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc move <gold><id>
<gray>Command used to set the location of an npc to your current location

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc near <gold><distance>
<gray>Command used to check which npcs are within a given radius around you

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc setlocation <gold><id> <x> <y> <z>
<gray>Command used to manually adjust an npc's location

@ -1,3 +0,0 @@
<gray>Usage <gold>» <yellow>/npc setrotation <gold><id> <yaw> <pitch>
<gray>Command used to manually adjust an npc's rotation

@ -1,3 +1,6 @@
<gray>Usage <gold>» <yellow>/npc teleport <gold><id> <gray>Examples:
<gold>* <yellow>/npc teleport <gold>cool_npc1
<gold>* <yellow>/npc teleport <gold>my_npc
<gold>* <yellow>/npc teleport <gold>12
<gray>Command used to teleport yourself to an npc <gray>Command used to teleport yourself to an npc

@ -1,3 +1,6 @@
<gray>Usage <gold>» <yellow>/npc toggle <gold><id> <gray>Examples:
<gold>* <yellow>/npc toggle <gold>cool_npc1
<gold>* <yellow>/npc toggle <gold>my_npc
<gold>* <yellow>/npc toggle <gold>12
<gray>Command used to enable or disable an npc <gray>Command used to enable or disable an npc

@ -1,3 +1,6 @@
<gray>Usage <gold>» <yellow>/npc type <gold><id> <type> <gray>Examples:
<gold>* <yellow>/npc type <gold>cool_npc1 zombie
<gold>* <yellow>/npc type <gold>my_npc skeleton
<gold>* <yellow>/npc type <gold>12 creeper
<gray>Command used to change the type of an npc <gray>Command used to change the type of an npc

@ -1,8 +1,6 @@
<gray>Usage <gold>» <yellow>/npc storage import <gold><importer> <gray>Examples:
<gold>* <yellow>/npc storage import <gold>znpcs
<gold>* <yellow>/npc storage import <gold>znpcsplus_legacy
<gold>* <yellow>/npc storage import <gold>citizens
<gray>Importers: <gray>Command used to import npcs from a different npc plugin
<gold>* <yellow>znpcs <gray>- Imports npcs from the ZNPCs plugin
<gold>* <yellow>znpcsplus_legacy <gray>- Imports npcs from legacy versions of ZNPCsPlus
<gold>* <yellow>citizens <gray>- Imports npcs from the Citizens plugin
<gray>Command used to import npcs from a different source

@ -1,4 +1,5 @@
<gray>Usage <gold>» <yellow>/npc storage reload <gray>Examples:
<gold>* <yellow>/npc storage reload
<gray>Command used to re-load all npcs from storage <gray>Command used to re-load all npcs from storage
<red>Warning: This command will delete all unsaved changes to npcs <red>Warning: This command will delete all unsaved changes to npcs

@ -1,3 +1,4 @@
<gray>Usage <gold>» <yellow>/npc storage save <gray>Examples:
<gold>* <yellow>/npc storage save
<gray>Command used to save the currently loaded npcs to storage <gray>Command used to save the currently loaded npcs to storage