diff --git a/README.md b/README.md index d1a7fbb..d947365 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci. ## Why is it so good? - 100% Packet Based - Nothing is ran on the main thread - Performance & stability oriented code -- Support for all versions from 1.8 to 1.20.2 +- Support for all versions from 1.8 to 1.20.4 - Support for multiple different storage options - Intuitive command system ### Requirements, Extensions & Supported Software Requirements: - Java 8+ -- Minecraft 1.8 - 1.20.2 +- Minecraft 1.8 - 1.20.4 Supported Softwares: - Spigot ([Website](https://www.spigotmc.org/)) diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java b/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java index cd10b37..65c34a1 100644 --- a/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java +++ b/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java @@ -1,5 +1,7 @@ package lol.pyr.znpcsplus.api.entity; +import org.bukkit.inventory.ItemStack; + import java.util.Set; /** @@ -32,10 +34,19 @@ public interface PropertyHolder { */ void setProperty(EntityProperty 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 * - * @return List of property keys + * @return Set of property keys */ Set> getAppliedProperties(); } diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/npc/Npc.java b/api/src/main/java/lol/pyr/znpcsplus/api/npc/Npc.java index 1c25a8d..540cd05 100644 --- a/api/src/main/java/lol/pyr/znpcsplus/api/npc/Npc.java +++ b/api/src/main/java/lol/pyr/znpcsplus/api/npc/Npc.java @@ -124,4 +124,10 @@ public interface Npc extends PropertyHolder { * @return The set of players that can currently see this npc */ Set getViewers(); + + /** + * Swings the entity's hand + * @param offHand Should the hand be the offhand + */ + void swingHand(boolean offHand); } diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/skin/Skin.java b/api/src/main/java/lol/pyr/znpcsplus/api/skin/Skin.java new file mode 100644 index 0000000..53ed651 --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/api/skin/Skin.java @@ -0,0 +1,6 @@ +package lol.pyr.znpcsplus.api.skin; + +public interface Skin { + String getTexture(); + String getSignature(); +} diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/skin/SkinDescriptor.java b/api/src/main/java/lol/pyr/znpcsplus/api/skin/SkinDescriptor.java index 9d849fe..2e93207 100644 --- a/api/src/main/java/lol/pyr/znpcsplus/api/skin/SkinDescriptor.java +++ b/api/src/main/java/lol/pyr/znpcsplus/api/skin/SkinDescriptor.java @@ -1,4 +1,11 @@ package lol.pyr.znpcsplus.api.skin; +import org.bukkit.entity.Player; + +import java.util.concurrent.CompletableFuture; + public interface SkinDescriptor { + CompletableFuture fetch(Player player); + Skin fetchInstant(Player player); + boolean supportsInstant(Player player); } diff --git a/build.gradle b/build.gradle index 5eff79a..8cf6bd7 100644 --- a/build.gradle +++ b/build.gradle @@ -33,9 +33,6 @@ subprojects { maven { url "https://repo.pyr.lol/releases" } - maven { - url "https://jitpack.io/" - } } publishing { diff --git a/plugin/build.gradle b/plugin/build.gradle index 6111cd6..93d3e5a 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -8,7 +8,7 @@ runServer { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } - minecraftVersion "1.20.2" + minecraftVersion "1.20.4" } processResources { @@ -31,17 +31,17 @@ publishing { } dependencies { - compileOnly "me.clip:placeholderapi:2.11.3" // Placeholder support + compileOnly "me.clip:placeholderapi:2.11.5" // Placeholder support compileOnly "com.google.code.gson:gson:2.10.1" // JSON parsing compileOnly "org.bstats:bstats-bukkit:3.0.2" // Plugin stats - compileOnly "com.github.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker - compileOnly "com.github.retrooper.packetevents:spigot:2.2.0" // Packets + compileOnly "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker + compileOnly "com.github.retrooper.packetevents:spigot:2.2.1" // Packets compileOnly "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs compileOnly "lol.pyr:director-adventure:2.1.1" // Commands // Fancy text library - compileOnly "net.kyori:adventure-platform-bukkit:4.3.1" - compileOnly "net.kyori:adventure-text-minimessage:4.14.0" + compileOnly "net.kyori:adventure-platform-bukkit:4.3.2" + compileOnly "net.kyori:adventure-text-minimessage:4.15.0" implementation "me.lucko:jar-relocator:1.7" implementation project(":api") @@ -69,4 +69,4 @@ shadowJar { minimize() } -tasks.assemble.dependsOn shadowJar \ No newline at end of file +tasks.assemble.dependsOn shadowJar diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java index 108fcd9..fd95cd0 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java @@ -99,16 +99,6 @@ public class ZNpcsPlus { PluginManager pluginManager = Bukkit.getPluginManager(); 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..."); packetEvents.init(); @@ -159,7 +149,6 @@ public class ZNpcsPlus { if (configManager.getConfig().checkForUpdates()) { UpdateChecker updateChecker = new UpdateChecker(getDescription()); scheduler.runDelayedTimerAsync(updateChecker, 5L, 6000L); - shutdownTasks.add(updateChecker::cancel); pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker, scheduler), bootstrap); } @@ -172,7 +161,7 @@ public class ZNpcsPlus { npcRegistry.reload(); if (configManager.getConfig().autoSaveEnabled()) shutdownTasks.add(npcRegistry::save); - if (legacy) { + if (bootstrap.movedLegacy()) { log(ChatColor.WHITE + " * Converting legacy data..."); try { Collection entries = importerRegistry.getImporter("znpcsplus_legacy").importData(); @@ -210,7 +199,12 @@ public class ZNpcsPlus { public void onDisable() { NpcApiProvider.unregister(); - for (Runnable runnable : shutdownTasks) runnable.run(); + for (Runnable runnable : shutdownTasks) try { + runnable.run(); + } catch (Throwable throwable) { + bootstrap.getLogger().severe("One of the registered shutdown tasks threw an exception:"); + throwable.printStackTrace(); + } shutdownTasks.clear(); PacketEvents.getAPI().terminate(); } @@ -219,7 +213,7 @@ public class ZNpcsPlus { HashMap> versions = new HashMap<>(); 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_19_2, LazyLoader.of(() -> new V1_19_2PacketFactory(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_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager))); ServerVersion version = packetEvents.getServerManager().getVersion(); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java index cfcd719..1dadd3e 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java @@ -6,18 +6,32 @@ import lol.pyr.znpcsplus.libraries.LibraryLoader; import lol.pyr.znpcsplus.util.FileUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; +import java.io.IOException; import java.io.Reader; +import java.nio.file.Files; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ZNpcsPlusBootstrap extends JavaPlugin { private ZNpcsPlus zNpcsPlus; + private boolean legacy; @Override 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"); LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries")); @@ -38,10 +52,10 @@ public class ZNpcsPlusBootstrap extends JavaPlugin { loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2"); loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2"); - loader.loadLibrary("com.github.robertlit", "SpigotResourcesAPI", "2.0", "https://jitpack.io"); + loader.loadLibrary("me.robertlit", "SpigotResourcesAPI", "2.0", "https://repo.pyr.lol/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.0", "https://repo.codemc.io/repository/maven-releases/"); + 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"), "spigot", "2.2.1", "https://repo.codemc.io/repository/maven-releases/"); loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1"); loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1"); @@ -49,19 +63,19 @@ public class ZNpcsPlusBootstrap extends JavaPlugin { loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.14.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.14.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.14.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.1"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.1"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.1"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.14.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.1"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.14.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.14.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.14.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.2"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.2"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.2"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.2"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson-legacy-impl", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json-legacy-impl", "4.15.0"); + loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.15.0"); loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0"); loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0"); loader.deleteUnloadedLibraries(); @@ -103,6 +117,10 @@ public class ZNpcsPlusBootstrap extends JavaPlugin { return context -> context.send(component); } + public boolean movedLegacy() { + return legacy; + } + // Ugly hack because of https://github.com/johnrengelman/shadow/issues/232 private static String decrypt(String packageName) { return packageName.replace("..", "."); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetLocationCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetLocationCommand.java index 91cde48..948d017 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetLocationCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetLocationCommand.java @@ -10,6 +10,7 @@ import lol.pyr.znpcsplus.util.NpcLocation; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,9 +25,9 @@ public class SetLocationCommand implements CommandHandler { public void run(CommandContext context) throws CommandExecutionException { context.setUsage(context.getLabel() + " setlocation "); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); - double x = context.parse(Double.class); - double y = context.parse(Double.class); - double z = context.parse(Double.class); + double x = parseLocation(context.popString(), npc.getLocation().getX()); + double y = parseLocation(context.popString(), npc.getLocation().getY()); + double z = parseLocation(context.popString(), npc.getLocation().getZ()); 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)); } @@ -34,6 +35,22 @@ public class SetLocationCommand implements CommandHandler { @Override public List suggest(CommandContext context) throws CommandExecutionException { 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(); } + + 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); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetRotationCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetRotationCommand.java index 99cb7ff..dda74dc 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetRotationCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/SetRotationCommand.java @@ -10,6 +10,7 @@ import lol.pyr.znpcsplus.util.NpcLocation; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,8 +25,12 @@ public class SetRotationCommand implements CommandHandler { public void run(CommandContext context) throws CommandExecutionException { context.setUsage(context.getLabel() + " setrotation "); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); - float yaw = context.parse(Float.class); - float pitch = context.parse(Float.class); + float yaw = parseRotation(context.popString(), npc.getLocation().getYaw()); + float pitch = parseRotation(context.popString(), npc.getLocation().getPitch()); + 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)); context.send(Component.text("NPC has been rotated to " + yaw + ", " + pitch + ".", NamedTextColor.GREEN)); } @@ -33,6 +38,21 @@ public class SetRotationCommand implements CommandHandler { @Override public List suggest(CommandContext context) throws CommandExecutionException { 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(); } + + 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); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/config/MainConfig.java b/plugin/src/main/java/lol/pyr/znpcsplus/config/MainConfig.java index 61d5d1f..7323e27 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/config/MainConfig.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/config/MainConfig.java @@ -68,4 +68,9 @@ public interface MainConfig { }) @DefaultInteger(60) 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(); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SkinTrait.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SkinTrait.java index 439c9ff..d21ab4e 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SkinTrait.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SkinTrait.java @@ -4,7 +4,7 @@ import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; import lol.pyr.znpcsplus.npc.NpcImpl; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import org.bukkit.configuration.ConfigurationSection; import org.jetbrains.annotations.NotNull; @@ -21,7 +21,7 @@ public class SkinTrait extends SectionCitizensTrait { public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { String texture = section.getString("textureRaw"); String signature = section.getString("signature"); - if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(texture, signature))); + if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(texture, signature))); return npc; } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java index 3ecdad1..3df75d0 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java @@ -8,9 +8,7 @@ import lol.pyr.znpcsplus.api.interaction.InteractionType; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.conversion.DataImporter; -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.conversion.znpcs.model.*; import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.hologram.HologramImpl; @@ -25,7 +23,7 @@ import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.scheduling.TaskScheduler; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; @@ -36,7 +34,6 @@ import lol.pyr.znpcsplus.util.LookType; import lol.pyr.znpcsplus.util.NpcLocation; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.DyeColor; import org.bukkit.inventory.ItemStack; @@ -57,6 +54,7 @@ public class ZNpcImporter implements DataImporter { private final EntityPropertyRegistryImpl propertyRegistry; private final MojangSkinCache skinCache; private final File dataFile; + private final File conversationFile; private final Gson gson; private final BungeeConnector bungeeConnector; @@ -73,6 +71,7 @@ public class ZNpcImporter implements DataImporter { this.propertyRegistry = propertyRegistry; this.skinCache = skinCache; this.dataFile = dataFile; + this.conversationFile = new File(dataFile.getParentFile(), "conversations.json"); this.bungeeConnector = bungeeConnector; gson = new GsonBuilder() .create(); @@ -89,6 +88,19 @@ public class ZNpcImporter implements DataImporter { 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 entries = new ArrayList<>(models.length); for (ZNpcsModel model : models) { String type = model.getNpcType(); @@ -108,6 +120,41 @@ public class ZNpcImporter implements DataImporter { NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, oldLoc.getWorld(), typeRegistry.getByName(type), location); 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(); hologram.setOffset(model.getHologramHeight()); for (String raw : model.getHologramLines()) { @@ -130,10 +177,10 @@ public class ZNpcImporter implements DataImporter { npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName())); } else if (model.getSkin() != null && model.getSignature() != null) { - npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new Skin(model.getSkin(), model.getSignature()))); + npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature()))); } - Map toggleValues = model.getNpcToggleValues(); + Map toggleValues = model.getNpcToggleValues() == null ? model.getNpcFunctions() : model.getNpcToggleValues(); if (toggleValues != null) { if (toggleValues.containsKey("look")) { npc.setProperty(propertyRegistry.getByName("look", LookType.class), LookType.CLOSEST_PLAYER); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversation.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversation.java new file mode 100644 index 0000000..eb80d28 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversation.java @@ -0,0 +1,16 @@ +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; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversationText.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversationText.java new file mode 100644 index 0000000..e9a85b5 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsConversationText.java @@ -0,0 +1,19 @@ +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; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsModel.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsModel.java index 60ec99b..67ecfa0 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsModel.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZNpcsModel.java @@ -14,12 +14,15 @@ public class ZNpcsModel { private String signature; private String glowName; + + private ZNpcsConversation conversation; private ZNpcsLocation location; private String npcType; private List hologramLines; private List clickActions; private Map npcEquip; private Map npcToggleValues; + private Map npcFunctions; private Map customizationMap; public int getId() { @@ -38,6 +41,10 @@ public class ZNpcsModel { return skinName; } + public ZNpcsConversation getConversation() { + return conversation; + } + public ZNpcsLocation getLocation() { return location; } @@ -62,6 +69,10 @@ public class ZNpcsModel { return npcToggleValues; } + public Map getNpcFunctions() { + return npcFunctions; + } + public Map getCustomizationMap() { return customizationMap; } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZnpcsConversations.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZnpcsConversations.java new file mode 100644 index 0000000..49cd913 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/model/ZnpcsConversations.java @@ -0,0 +1,23 @@ +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; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java index 9818194..013e2cc 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -92,18 +92,10 @@ 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 - // End Crystal - registerType("beam_target", null); // TODO: Make a block pos class for this - registerType("show_base", true); // TODO - // Enderman registerType("enderman_held_block", new BlockState(0)); // TODO: figure out the type on this registerType("enderman_screaming", 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 - */ } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java index a827221..97ef4de 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java @@ -10,6 +10,7 @@ import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.util.NpcLocation; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import java.util.Collection; import java.util.Set; @@ -72,6 +73,10 @@ public class PacketEntity implements PropertyHolder { packetFactory.sendAllMetadata(player, this, properties); } + public void swingHand(Player player, boolean offhand) { + packetFactory.sendHandSwing(player, this, offhand); + } + private static int reserveEntityID() { if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14)) { return Reflections.ATOMIC_ENTITY_ID_FIELD.get().incrementAndGet(); @@ -97,6 +102,11 @@ public class PacketEntity implements PropertyHolder { properties.setProperty(key, value); } + @Override + public void setItemProperty(EntityProperty key, ItemStack value) { + properties.setItemProperty(key, value); + } + @Override public Set> getAppliedProperties() { return properties.getAppliedProperties(); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java index e1816e0..d26efa1 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java @@ -106,7 +106,7 @@ public class HologramImpl extends Viewable implements Hologram { } public void insertTextLine(int index, String line) { - insertTextLineComponent(index, textSerializer.deserialize(line)); + insertTextLineComponent(index, textSerializer.deserialize(textSerializer.serialize(MiniMessage.miniMessage().deserialize(line)))); } public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java index 9bb695f..f2652f0 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java @@ -7,6 +7,7 @@ import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.util.NpcLocation; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import java.util.Collection; import java.util.HashSet; @@ -70,6 +71,11 @@ public class HologramLine implements PropertyHolder { 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 public Set> getAppliedProperties() { return properties; diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java index 445fc1f..fcc563d 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java @@ -1,6 +1,7 @@ package lol.pyr.znpcsplus.npc; 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.npc.Npc; import lol.pyr.znpcsplus.api.npc.NpcType; @@ -18,6 +19,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import java.util.*; import java.util.stream.Collectors; @@ -146,9 +148,20 @@ public class NpcImpl extends Viewable implements Npc { return propertyMap.containsKey((EntityPropertyImpl) key); } + @SuppressWarnings("unchecked") @Override public void setProperty(EntityProperty key, T value) { - setProperty((EntityPropertyImpl) key, value ); + // See https://github.com/Pyrbu/ZNPCsPlus/pull/129#issuecomment-1948777764 + Object val = value; + if (val instanceof ItemStack) val = SpigotConversionUtil.fromBukkitItemStack((ItemStack) val); + + setProperty((EntityPropertyImpl) key, (T) val); + } + + @SuppressWarnings("unchecked") + @Override + public void setItemProperty(EntityProperty key, ItemStack value) { + setProperty((EntityPropertyImpl) key, SpigotConversionUtil.fromBukkitItemStack(value)); } public void setProperty(EntityPropertyImpl key, T value) { @@ -201,4 +214,8 @@ public class NpcImpl extends Viewable implements Npc { delete(); this.worldName = world.getName(); } + + public void swingHand(boolean offHand) { + for (Player viewer : getViewers()) entity.swingHand(viewer, offHand); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java index b8a00f6..3e5119d 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -156,7 +156,7 @@ public class NpcTypeImpl implements NpcType { } if (EntityTypes.isTypeInstanceOf(type, EntityTypes.PANDA)) { if (version.isNewerThanOrEquals(ServerVersion.V_1_15)) { - addProperties("panda_rolling", "panda_sitting", "panda_on_back"); + addProperties("panda_rolling", "panda_sitting", "panda_on_back", "hand"); } else { addProperties("panda_eating"); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java index 6fe3fde..98cc382 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java @@ -23,4 +23,5 @@ public interface PacketFactory { void sendEquipment(Player player, PacketEntity entity, Equipment equipment); void sendMetadata(Player player, PacketEntity entity, List data); void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch); + void sendHandSwing(Player player, PacketEntity entity, boolean offHand); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_2PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_3PacketFactory.java similarity index 87% rename from plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_2PacketFactory.java rename to plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_3PacketFactory.java index 8dddef0..22fe62d 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_2PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19_3PacketFactory.java @@ -19,8 +19,8 @@ import org.bukkit.plugin.Plugin; import java.util.EnumSet; import java.util.concurrent.CompletableFuture; -public class V1_19_2PacketFactory extends V1_17PacketFactory { - public V1_19_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { +public class V1_19_3PacketFactory extends V1_17PacketFactory { + public V1_19_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager); } @@ -30,7 +30,8 @@ public class V1_19_2PacketFactory extends V1_17PacketFactory { CompletableFuture future = new CompletableFuture<>(); skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> { WrapperPlayServerPlayerInfoUpdate.PlayerInfo info = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo( - profile, false, 1, GameMode.CREATIVE, Component.empty(), null); + profile, false, 1, GameMode.CREATIVE, + Component.text(configManager.getConfig().tabDisplayName().replace("{id}", Integer.toString(entity.getEntityId()))), null); sendPacket(player, new WrapperPlayServerPlayerInfoUpdate(EnumSet.of(WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER, WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_LISTED), info, info)); future.complete(null); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java index 761476f..fd9e126 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java @@ -17,12 +17,13 @@ import org.bukkit.plugin.Plugin; import java.util.Optional; -public class V1_20_2PacketFactory extends V1_19_2PacketFactory { +public class V1_20_2PacketFactory extends V1_19_3PacketFactory { protected ConfigManager configManager; public V1_20_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager); + this.configManager = configManager; } @Override diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java index f06994c..74490ce 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java @@ -96,7 +96,8 @@ public class V1_8PacketFactory implements PacketFactory { CompletableFuture future = new CompletableFuture<>(); skinned(player, properties, new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId()))).thenAccept(profile -> { sendPacket(player, new WrapperPlayServerPlayerInfo( - WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(Component.text(" "), + WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData( + Component.text(configManager.getConfig().tabDisplayName().replace("{id}", Integer.toString(entity.getEntityId()))), profile, GameMode.CREATIVE, 1))); future.complete(null); }); @@ -114,7 +115,7 @@ public class V1_8PacketFactory implements PacketFactory { @Override public void createTeam(Player player, PacketEntity entity, NamedColor namedColor) { sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.CREATE, new WrapperPlayServerTeams.ScoreBoardTeamInfo( - Component.empty(), Component.empty(), Component.empty(), + Component.text(" "), null, null, WrapperPlayServerTeams.NameTagVisibility.NEVER, WrapperPlayServerTeams.CollisionRule.NEVER, namedColor == null ? NamedTextColor.WHITE : NamedTextColor.NAMES.value(namedColor.name().toLowerCase()), @@ -174,4 +175,11 @@ public class V1_8PacketFactory implements PacketFactory { protected void add(Map map, EntityData 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)); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/BaseSkinDescriptor.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/BaseSkinDescriptor.java index c98064b..3c63364 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/BaseSkinDescriptor.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/BaseSkinDescriptor.java @@ -13,8 +13,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; public interface BaseSkinDescriptor extends SkinDescriptor { - CompletableFuture fetch(Player player); - Skin fetchInstant(Player player); + CompletableFuture fetch(Player player); + SkinImpl fetchInstant(Player player); boolean supportsInstant(Player player); String serialize(); @@ -27,7 +27,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor { for (int i = 0; i < (arr.length - 1) / 3; i++) { properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3])); } - return new PrefetchedDescriptor(new Skin(properties)); + return new PrefetchedDescriptor(new SkinImpl(properties)); } throw new IllegalArgumentException("Unknown SkinDescriptor type!"); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptorFactoryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptorFactoryImpl.java index b6656ed..0eeb491 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptorFactoryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinDescriptorFactoryImpl.java @@ -36,7 +36,7 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory { @Override public SkinDescriptor createStaticDescriptor(String texture, String signature) { - return new PrefetchedDescriptor(new Skin(texture, signature)); + return new PrefetchedDescriptor(new SkinImpl(texture, signature)); } @Override diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/Skin.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinImpl.java similarity index 79% rename from plugin/src/main/java/lol/pyr/znpcsplus/skin/Skin.java rename to plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinImpl.java index 356bb91..66bc999 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/Skin.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/SkinImpl.java @@ -6,6 +6,7 @@ import com.github.retrooper.packetevents.protocol.player.TextureProperty; import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import lol.pyr.znpcsplus.api.skin.Skin; import lol.pyr.znpcsplus.reflection.Reflections; import java.lang.reflect.InvocationTargetException; @@ -13,21 +14,21 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class Skin { +public class SkinImpl implements Skin { private final long timestamp = System.currentTimeMillis(); private final List properties; private static final boolean V1_20_2 = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2); - public Skin(String texture, String signature) { + public SkinImpl(String texture, String signature) { properties = new ArrayList<>(1); properties.add(new TextureProperty("textures", texture, signature)); } - public Skin(Collection properties) { + public SkinImpl(Collection properties) { this.properties = new ArrayList<>(properties); } - public Skin(Object propertyMap) { + public SkinImpl(Object propertyMap) { this.properties = new ArrayList<>(); try { Collection properties = (Collection) Reflections.PROPERTY_MAP_VALUES_METHOD.get().invoke(propertyMap); @@ -51,7 +52,7 @@ public class Skin { } } - public Skin(JsonObject obj) { + public SkinImpl(JsonObject obj) { properties = new ArrayList<>(); for (JsonElement e : obj.get("properties").getAsJsonArray()) { JsonObject o = e.getAsJsonObject(); @@ -71,4 +72,20 @@ public class Skin { public boolean isExpired() { 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; + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/cache/MojangSkinCache.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/cache/MojangSkinCache.java index de90ca5..2a54509 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/cache/MojangSkinCache.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/cache/MojangSkinCache.java @@ -4,7 +4,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.reflection.Reflections; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -24,7 +24,7 @@ public class MojangSkinCache { private final ConfigManager configManager; - private final Map cache = new ConcurrentHashMap<>(); + private final Map cache = new ConcurrentHashMap<>(); private final Map idCache = new ConcurrentHashMap<>(); public MojangSkinCache(ConfigManager configManager) { @@ -32,11 +32,11 @@ public class MojangSkinCache { } public void cleanCache() { - for (Map.Entry entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); + for (Map.Entry entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry entry : idCache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); } - public CompletableFuture fetchByName(String name) { + public CompletableFuture fetchByName(String name) { Player player = Bukkit.getPlayerExact(name); 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(); String id = obj.get("id").getAsString(); idCache.put(name.toLowerCase(), new CachedId(id)); - Skin skin = fetchByUUID(id).join(); + SkinImpl skin = fetchByUUID(id).join(); if (skin == null) return fetchByNameFallback(name).join(); return skin; } @@ -69,7 +69,7 @@ public class MojangSkinCache { }); } - public CompletableFuture fetchByNameFallback(String name) { + public CompletableFuture fetchByNameFallback(String name) { Player player = Bukkit.getPlayerExact(name); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); @@ -89,7 +89,7 @@ public class MojangSkinCache { JsonObject textures = obj.get("textures").getAsJsonObject(); String value = textures.get("raw").getAsJsonObject().get("value").getAsString(); String signature = textures.get("raw").getAsJsonObject().get("signature").getAsString(); - Skin skin = new Skin(value, signature); + SkinImpl skin = new SkinImpl(value, signature); cache.put(uuid, skin); return skin; } @@ -105,7 +105,7 @@ public class MojangSkinCache { }); } - public CompletableFuture fetchByUrl(URL url, String variant) { + public CompletableFuture fetchByUrl(URL url, String variant) { return CompletableFuture.supplyAsync(() -> { URL apiUrl = parseUrl("https://api.mineskin.org/generate/url"); HttpURLConnection connection = null; @@ -127,7 +127,7 @@ public class MojangSkinCache { if (obj.has("error")) return null; if (!obj.has("data")) return null; JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject(); - return new Skin(texture.get("value").getAsString(), texture.get("signature").getAsString()); + return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString()); } } catch (IOException exception) { @@ -147,26 +147,26 @@ public class MojangSkinCache { if (!idCache.containsKey(name)) return false; CachedId id = idCache.get(name); if (id.isExpired() || !cache.containsKey(id.getId())) return false; - Skin skin = cache.get(id.getId()); + SkinImpl skin = cache.get(id.getId()); return !skin.isExpired(); } - public Skin getFullyCachedByName(String s) { + public SkinImpl getFullyCachedByName(String s) { String name = s.toLowerCase(); if (!idCache.containsKey(name)) return null; CachedId id = idCache.get(name); if (id.isExpired() || !cache.containsKey(id.getId())) return null; - Skin skin = cache.get(id.getId()); + SkinImpl skin = cache.get(id.getId()); if (skin.isExpired()) return null; return skin; } - public CompletableFuture fetchByUUID(String uuid) { + public CompletableFuture fetchByUUID(String uuid) { Player player = Bukkit.getPlayer(uuid); if (player != null && player.isOnline()) return CompletableFuture.completedFuture(getFromPlayer(player)); if (cache.containsKey(uuid)) { - Skin skin = cache.get(uuid); + SkinImpl skin = cache.get(uuid); if (!skin.isExpired()) return CompletableFuture.completedFuture(skin); } @@ -179,7 +179,7 @@ public class MojangSkinCache { try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) { JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject(); if (obj.has("errorMessage")) return null; - Skin skin = new Skin(obj); + SkinImpl skin = new SkinImpl(obj); cache.put(uuid, skin); return skin; } @@ -195,12 +195,12 @@ public class MojangSkinCache { }); } - public Skin getFromPlayer(Player player) { + public SkinImpl getFromPlayer(Player player) { try { Object playerHandle = Reflections.GET_PLAYER_HANDLE_METHOD.get().invoke(player); Object gameProfile = Reflections.GET_PROFILE_METHOD.get().invoke(playerHandle); Object propertyMap = Reflections.GET_PROPERTY_MAP_METHOD.get().invoke(gameProfile); - return new Skin(propertyMap); + return new SkinImpl(propertyMap); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/FetchingDescriptor.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/FetchingDescriptor.java index 17364a2..d99ad2c 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/FetchingDescriptor.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/FetchingDescriptor.java @@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.util.PapiUtil; import org.bukkit.entity.Player; @@ -19,12 +19,12 @@ public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor { } @Override - public CompletableFuture fetch(Player player) { + public CompletableFuture fetch(Player player) { return skinCache.fetchByName(PapiUtil.set(player, name)); } @Override - public Skin fetchInstant(Player player) { + public SkinImpl fetchInstant(Player player) { return skinCache.getFullyCachedByName(PapiUtil.set(player, name)); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/MirrorDescriptor.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/MirrorDescriptor.java index 247015f..8971d0b 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/MirrorDescriptor.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/MirrorDescriptor.java @@ -2,7 +2,7 @@ package lol.pyr.znpcsplus.skin.descriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import org.bukkit.entity.Player; @@ -16,12 +16,12 @@ public class MirrorDescriptor implements BaseSkinDescriptor, SkinDescriptor { } @Override - public CompletableFuture fetch(Player player) { + public CompletableFuture fetch(Player player) { return CompletableFuture.completedFuture(skinCache.getFromPlayer(player)); } @Override - public Skin fetchInstant(Player player) { + public SkinImpl fetchInstant(Player player) { return skinCache.getFromPlayer(player); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/PrefetchedDescriptor.java b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/PrefetchedDescriptor.java index 3e177dd..c852080 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/PrefetchedDescriptor.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/skin/descriptor/PrefetchedDescriptor.java @@ -3,7 +3,7 @@ package lol.pyr.znpcsplus.skin.descriptor; import com.github.retrooper.packetevents.protocol.player.TextureProperty; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; -import lol.pyr.znpcsplus.skin.Skin; +import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import org.bukkit.entity.Player; @@ -11,9 +11,9 @@ import java.net.URL; import java.util.concurrent.CompletableFuture; public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor { - private final Skin skin; + private final SkinImpl skin; - public PrefetchedDescriptor(Skin skin) { + public PrefetchedDescriptor(SkinImpl skin) { this.skin = skin; } @@ -26,12 +26,12 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor } @Override - public CompletableFuture fetch(Player player) { + public CompletableFuture fetch(Player player) { return CompletableFuture.completedFuture(skin); } @Override - public Skin fetchInstant(Player player) { + public SkinImpl fetchInstant(Player player) { return skin; } @@ -40,7 +40,7 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor return true; } - public Skin getSkin() { + public SkinImpl getSkin() { return skin; } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/updater/UpdateChecker.java b/plugin/src/main/java/lol/pyr/znpcsplus/updater/UpdateChecker.java index c84202f..0503347 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/updater/UpdateChecker.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/updater/UpdateChecker.java @@ -28,10 +28,7 @@ public class UpdateChecker extends BukkitRunnable { if (resource == null) return; newestVersion = resource.getVersion(); - int current = versionToNumber(info.getVersion()); - int newest = versionToNumber(newestVersion); - - status = current >= newest ? Status.LATEST_VERSION : Status.UPDATE_NEEDED; + status = compareVersions(info.getVersion(), newestVersion); if (status == Status.UPDATE_NEEDED) notifyConsole(); } @@ -40,10 +37,43 @@ public class UpdateChecker extends BukkitRunnable { logger.warning("Download it at " + UpdateChecker.DOWNLOAD_LINK); } - private int versionToNumber(String version) { - int num = Integer.parseInt(version.replaceAll("[^0-9]", "")); - if (version.toLowerCase().contains("snapshot")) num -= 1; - return num; + private Status compareVersions(String currentVersion, String newVersion) { + if (currentVersion.equalsIgnoreCase(newVersion)) return Status.LATEST_VERSION; + ReleaseType currentType = parseReleaseType(currentVersion); + ReleaseType newType = parseReleaseType(newVersion); + 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() { @@ -57,4 +87,8 @@ public class UpdateChecker extends BukkitRunnable { public enum Status { UNKNOWN, LATEST_VERSION, UPDATE_NEEDED } + + public enum ReleaseType { + UNKNOWN, SNAPSHOT, ALPHA, BETA, RELEASE + } } diff --git a/plugin/src/main/resources/messages/property-hover/remove.txt b/plugin/src/main/resources/messages/property-hover/remove.txt index e69de29..20f6830 100644 --- a/plugin/src/main/resources/messages/property-hover/remove.txt +++ b/plugin/src/main/resources/messages/property-hover/remove.txt @@ -0,0 +1,3 @@ +Usage » /npc property remove + +Command used to unset properties on npcs \ No newline at end of file diff --git a/plugin/src/main/resources/messages/property-hover/set.txt b/plugin/src/main/resources/messages/property-hover/set.txt index e69de29..9f2888e 100644 --- a/plugin/src/main/resources/messages/property-hover/set.txt +++ b/plugin/src/main/resources/messages/property-hover/set.txt @@ -0,0 +1,3 @@ +Usage » /npc property set + +Command used to customize npcs with custom properties \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/center.txt b/plugin/src/main/resources/messages/root-hover/center.txt index e69de29..0fa96f1 100644 --- a/plugin/src/main/resources/messages/root-hover/center.txt +++ b/plugin/src/main/resources/messages/root-hover/center.txt @@ -0,0 +1,3 @@ +Usage » /npc center + +Command used to move an npc to the center of the block it's currently occupying \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/changeid.txt b/plugin/src/main/resources/messages/root-hover/changeid.txt index e69de29..a819ff2 100644 --- a/plugin/src/main/resources/messages/root-hover/changeid.txt +++ b/plugin/src/main/resources/messages/root-hover/changeid.txt @@ -0,0 +1,3 @@ +Usage » /npc changeid + +Command used to change the id of an npc \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/create.txt b/plugin/src/main/resources/messages/root-hover/create.txt index e69de29..4eb58b7 100644 --- a/plugin/src/main/resources/messages/root-hover/create.txt +++ b/plugin/src/main/resources/messages/root-hover/create.txt @@ -0,0 +1,3 @@ +Usage » /npc create + +Command used to create an npc of a given type \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/delete.txt b/plugin/src/main/resources/messages/root-hover/delete.txt index e69de29..69d0cdb 100644 --- a/plugin/src/main/resources/messages/root-hover/delete.txt +++ b/plugin/src/main/resources/messages/root-hover/delete.txt @@ -0,0 +1,3 @@ +Usage » /npc delete + +Command used to delete an npc \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/list.txt b/plugin/src/main/resources/messages/root-hover/list.txt index e69de29..4a2f00e 100644 --- a/plugin/src/main/resources/messages/root-hover/list.txt +++ b/plugin/src/main/resources/messages/root-hover/list.txt @@ -0,0 +1,3 @@ +Usage » /npc list + +Command used to list all npcs \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/lookatme.txt b/plugin/src/main/resources/messages/root-hover/lookatme.txt index e69de29..23c60a6 100644 --- a/plugin/src/main/resources/messages/root-hover/lookatme.txt +++ b/plugin/src/main/resources/messages/root-hover/lookatme.txt @@ -0,0 +1,3 @@ +Usage » /npc lookatme + +Command used to set the rotation of an npc to be looking at your current location \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/move.txt b/plugin/src/main/resources/messages/root-hover/move.txt index e69de29..4edbf67 100644 --- a/plugin/src/main/resources/messages/root-hover/move.txt +++ b/plugin/src/main/resources/messages/root-hover/move.txt @@ -0,0 +1,3 @@ +Usage » /npc move + +Command used to set the location of an npc to your current location \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/near.txt b/plugin/src/main/resources/messages/root-hover/near.txt index e69de29..6129a95 100644 --- a/plugin/src/main/resources/messages/root-hover/near.txt +++ b/plugin/src/main/resources/messages/root-hover/near.txt @@ -0,0 +1,3 @@ +Usage » /npc near + +Command used to check which npcs are within a given radius around you \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/setlocation.txt b/plugin/src/main/resources/messages/root-hover/setlocation.txt index e69de29..7b085f6 100644 --- a/plugin/src/main/resources/messages/root-hover/setlocation.txt +++ b/plugin/src/main/resources/messages/root-hover/setlocation.txt @@ -0,0 +1,3 @@ +Usage » /npc setlocation + +Command used to manually adjust an npc's location \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/setrotation.txt b/plugin/src/main/resources/messages/root-hover/setrotation.txt index e69de29..de1f808 100644 --- a/plugin/src/main/resources/messages/root-hover/setrotation.txt +++ b/plugin/src/main/resources/messages/root-hover/setrotation.txt @@ -0,0 +1,3 @@ +Usage » /npc setrotation + +Command used to manually adjust an npc's rotation \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/teleport.txt b/plugin/src/main/resources/messages/root-hover/teleport.txt index 4e33f62..6c4a944 100644 --- a/plugin/src/main/resources/messages/root-hover/teleport.txt +++ b/plugin/src/main/resources/messages/root-hover/teleport.txt @@ -1,6 +1,3 @@ -Examples: - * /npc teleport cool_npc1 - * /npc teleport my_npc - * /npc teleport 12 +Usage » /npc teleport Command used to teleport yourself to an npc \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/toggle.txt b/plugin/src/main/resources/messages/root-hover/toggle.txt index 21bb7ad..85cd1de 100644 --- a/plugin/src/main/resources/messages/root-hover/toggle.txt +++ b/plugin/src/main/resources/messages/root-hover/toggle.txt @@ -1,6 +1,3 @@ -Examples: - * /npc toggle cool_npc1 - * /npc toggle my_npc - * /npc toggle 12 +Usage » /npc toggle Command used to enable or disable an npc \ No newline at end of file diff --git a/plugin/src/main/resources/messages/root-hover/type.txt b/plugin/src/main/resources/messages/root-hover/type.txt index 90a123a..93fc3f1 100644 --- a/plugin/src/main/resources/messages/root-hover/type.txt +++ b/plugin/src/main/resources/messages/root-hover/type.txt @@ -1,6 +1,3 @@ -Examples: - * /npc type cool_npc1 zombie - * /npc type my_npc skeleton - * /npc type 12 creeper +Usage » /npc type Command used to change the type of an npc \ No newline at end of file diff --git a/plugin/src/main/resources/messages/storage-hover/import.txt b/plugin/src/main/resources/messages/storage-hover/import.txt index ac114a5..0f758c2 100644 --- a/plugin/src/main/resources/messages/storage-hover/import.txt +++ b/plugin/src/main/resources/messages/storage-hover/import.txt @@ -1,6 +1,8 @@ -Examples: - * /npc storage import znpcs - * /npc storage import znpcsplus_legacy - * /npc storage import citizens +Usage » /npc storage import -Command used to import npcs from a different npc plugin \ No newline at end of file +Importers: + * znpcs - Imports npcs from the ZNPCs plugin + * znpcsplus_legacy - Imports npcs from legacy versions of ZNPCsPlus + * citizens - Imports npcs from the Citizens plugin + +Command used to import npcs from a different source \ No newline at end of file diff --git a/plugin/src/main/resources/messages/storage-hover/reload.txt b/plugin/src/main/resources/messages/storage-hover/reload.txt index ab56365..45d05a7 100644 --- a/plugin/src/main/resources/messages/storage-hover/reload.txt +++ b/plugin/src/main/resources/messages/storage-hover/reload.txt @@ -1,5 +1,4 @@ -Examples: - * /npc storage reload +Usage » /npc storage reload Command used to re-load all npcs from storage Warning: This command will delete all unsaved changes to npcs \ No newline at end of file diff --git a/plugin/src/main/resources/messages/storage-hover/save.txt b/plugin/src/main/resources/messages/storage-hover/save.txt index 40faf0a..a255ede 100644 --- a/plugin/src/main/resources/messages/storage-hover/save.txt +++ b/plugin/src/main/resources/messages/storage-hover/save.txt @@ -1,4 +1,3 @@ -Examples: - * /npc storage save +Usage » /npc storage save Command used to save the currently loaded npcs to storage \ No newline at end of file