Compare commits

...

44 Commits

Author SHA1 Message Date
4a965def75 Merge remote-tracking branch 'upstream/2.X' into 2.X
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-30 20:06:37 +01:00
D3v1s0m
57e11cb186
enhance UpdateChecker to support alpha/beta versions and snapshots 2024-03-10 20:47:46 +05:30
D3v1s0m
220e4a3f58
remove implemented properties comments 2024-03-10 18:18:22 +05:30
Pyrbu
5213476d15 add hand swinging 2024-02-16 18:35:00 +01:00
Pyrbu
828a8a3465 Merge branch 'alexdev032.X' into 2.X 2024-02-16 17:46:57 +01:00
Pyr
65a8b36e7b
Merge pull request #127 from NoltoxGit/upgrade-packetevents
Update libraries
2024-02-16 16:45:57 +00:00
Pyrbu
633dc76592 Merge remote-tracking branch 'Noltox/upgrade-packetevents' into upgrade-packetevents 2024-02-16 17:45:09 +01:00
Noltox
1d1271b488 Update ZNpcsPlusBootstrap.java 2024-02-16 17:44:16 +01:00
Noltox
1eaa628769 Update ZNpcsPlusBootstrap.java 2024-02-16 17:44:16 +01:00
Noltox
fea49ea0bc Update build.gradle
Update various dependencies
2024-02-16 17:44:16 +01:00
Pyrbu
1bfdec206e revert packetevents update 2024-02-16 17:33:38 +01:00
Pyrbu
22108fd6d9 revert blacklist api 2024-02-16 17:33:28 +01:00
Pyrbu
3ebd0070b8 Merge remote-tracking branch 'alexdev03/2.X' into alexdev032.X
# Conflicts:
#	plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java
2024-02-16 17:31:13 +01:00
Pyrbu
c4bb24c888 fix itemstack properties in the api 2024-02-16 17:29:44 +01:00
Pyrbu
00de9ef636 revert packetevents update 2024-02-16 17:25:37 +01:00
Pyrbu
a2400b42f5 revert blacklist api 2024-02-16 17:25:37 +01:00
AlexDev_
e7d12f8192 Fixed itemstack/equipment error
Fixed packet events error
Added blacklist api methods
2024-02-16 17:25:37 +01:00
Pyrbu
c3a83b6d04 just dont do anything with the update checker on shutdown 2024-02-16 16:23:11 +01:00
AlexDev_
475c49c7e2 Fixed itemstack/equipment error
Fixed packet events error
Added blacklist api methods
2024-02-13 22:48:34 +01:00
Noltox
e230400023
Update ZNpcsPlusBootstrap.java 2024-02-12 00:39:12 +01:00
Noltox
c749d127dd
Update ZNpcsPlusBootstrap.java 2024-02-12 00:28:12 +01:00
Noltox
8809ef42f4
Update build.gradle
Update various dependencies
2024-02-12 00:27:30 +01:00
Pyrbu
abfdc1901b move spigot resources api to my repo to remove jitpack 2024-01-31 17:35:57 +01:00
Pyrbu
625f0611c9 downgrade packetevents 2024-01-31 17:04:48 +01:00
Pyrbu
4137337365 make 1.19+ tab display name consistent with below 2024-01-31 07:02:32 +01:00
Pyr
cb419227d7
Merge pull request #125 from vLuckyyy/Fix-minimessages-in-set-command
Fix MiniMessages doesn't parse correctly when people uses holo set command instead of add.
2024-01-31 05:23:17 +00:00
Pyr
b808ec7a49
Merge pull request #124 from vLuckyyy/add-option-to-panda-hand
Add option to hand property for panda npc.
2024-01-31 05:22:43 +00:00
Pyr
35ad35fb57
Merge pull request #119 from MineFact/2.X
Fix migration issues
2024-01-31 05:22:30 +00:00
D3v1s0m
624a3c97a2
fix config manager initialization in V1_20_2PacketFactory 2024-01-29 00:06:07 +05:30
Pyrbu
0c0747cd23 attempt to fix empty components 2024-01-28 04:11:04 +01:00
Martin Sulikowski
7c7205f5fc
Fix MiniMessages doesn't parse correctly when people uses holo set command instead of add. 2024-01-26 02:24:51 +01:00
Martin Sulikowski
dadd36df05
Add option to hand property for panda npc. 2024-01-24 22:50:37 +01:00
MineFact
7ebb456e1b convert npc conversations from conversations.json as well 2024-01-12 17:55:22 +01:00
MineFact
0f17060b49 the npcToggleValues value in data.json can also be called npcFunctions 2024-01-12 17:53:11 +01:00
D3v1s0m
ecec49f2bb
set location and rotation command enhancement and tab complete 2024-01-10 00:34:24 +05:30
Pyrbu
da4e46497c hover stuff 2024-01-09 10:56:50 +01:00
Pyrbu
683e48a974 make shutdowns safer 2024-01-08 09:37:31 +01:00
Pyrbu
2a1f44b1bb expose skin texture methods in api 2024-01-07 18:26:17 +01:00
Pyrbu
c560fe597e actually fix update checker not scheduled yet error on fast shutdowns 2024-01-04 19:55:27 +01:00
Pyrbu
3bfea0a004 fix update checker shutdown errors 2023-12-30 20:35:33 +01:00
D3v1s0m
e893beb22f
fix npc spawning in 1.19.2 2023-12-27 21:31:05 +05:30
D3v1s0m
2686cd4bb6
move legacy data before downloading libraries 2023-12-27 20:08:35 +05:30
D3v1s0m
5501e3a500
Added config option for tab display name 2023-12-23 16:28:26 +05:30
Pyr
5b1a698d0f
[ci skip] Update README.md 2023-12-21 13:06:52 +01:00
54 changed files with 452 additions and 141 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.2 - Support for all versions from 1.8 to 1.20.4
- 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.2 - Minecraft 1.8 - 1.20.4
Supported Softwares: Supported Softwares:
- Spigot ([Website](https://www.spigotmc.org/)) - Spigot ([Website](https://www.spigotmc.org/))

@ -1,5 +1,7 @@
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;
/** /**
@ -32,10 +34,19 @@ 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 List of property keys * @return Set of property keys
*/ */
Set<EntityProperty<?>> getAppliedProperties(); Set<EntityProperty<?>> getAppliedProperties();
} }

@ -124,4 +124,10 @@ 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);
} }

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

@ -1,4 +1,11 @@
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,9 +33,6 @@ 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.2" minecraftVersion "1.20.4"
} }
processResources { processResources {
@ -31,17 +31,17 @@ publishing {
} }
dependencies { 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 "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 "com.github.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker compileOnly "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker
compileOnly "com.github.retrooper.packetevents:spigot:2.2.0" // Packets compileOnly "com.github.retrooper.packetevents:spigot:2.2.1" // 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.1" compileOnly "net.kyori:adventure-platform-bukkit:4.3.2"
compileOnly "net.kyori:adventure-text-minimessage:4.14.0" compileOnly "net.kyori:adventure-text-minimessage:4.15.0"
implementation "me.lucko:jar-relocator:1.7" implementation "me.lucko:jar-relocator:1.7"
implementation project(":api") implementation project(":api")
@ -69,4 +69,4 @@ shadowJar {
minimize() minimize()
} }
tasks.assemble.dependsOn shadowJar tasks.assemble.dependsOn shadowJar

@ -99,16 +99,6 @@ 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();
@ -159,7 +149,6 @@ 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);
} }
@ -172,7 +161,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 (legacy) { if (bootstrap.movedLegacy()) {
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();
@ -210,7 +199,12 @@ public class ZNpcsPlus {
public void onDisable() { public void onDisable() {
NpcApiProvider.unregister(); 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(); shutdownTasks.clear();
PacketEvents.getAPI().terminate(); PacketEvents.getAPI().terminate();
} }
@ -219,7 +213,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_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))); 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,18 +6,32 @@ 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"));
@ -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-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("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"), "api", "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("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-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");
@ -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("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-api", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.14.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.14.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.1"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.1"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.1"); loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.14.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.1"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.2");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.14.0"); loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "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-gson-legacy-impl", "4.15.0");
loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.14.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.14.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.14.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-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();
@ -103,6 +117,10 @@ 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,6 +10,7 @@ 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;
@ -24,9 +25,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 = context.parse(Double.class); double x = parseLocation(context.popString(), npc.getLocation().getX());
double y = context.parse(Double.class); double y = parseLocation(context.popString(), npc.getLocation().getY());
double z = context.parse(Double.class); double z = parseLocation(context.popString(), npc.getLocation().getZ());
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));
} }
@ -34,6 +35,22 @@ 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,6 +10,7 @@ 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;
@ -24,8 +25,12 @@ 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 = context.parse(Float.class); float yaw = parseRotation(context.popString(), npc.getLocation().getYaw());
float pitch = context.parse(Float.class); 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)); 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));
} }
@ -33,6 +38,21 @@ 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,4 +68,9 @@ 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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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 Skin(texture, signature))); if (texture != null && signature != null) npc.setProperty(registry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(texture, signature)));
return npc; return npc;
} }
} }

@ -8,9 +8,7 @@ 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.ZNpcsAction; import lol.pyr.znpcsplus.conversion.znpcs.model.*;
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;
@ -25,7 +23,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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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;
@ -36,7 +34,6 @@ 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;
@ -57,6 +54,7 @@ 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;
@ -73,6 +71,7 @@ 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();
@ -89,6 +88,19 @@ 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();
@ -108,6 +120,41 @@ 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()) {
@ -130,10 +177,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 Skin(model.getSkin(), model.getSignature()))); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature())));
} }
Map<String, Object> toggleValues = model.getNpcToggleValues(); Map<String, Object> toggleValues = model.getNpcToggleValues() == null ? model.getNpcFunctions() : 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);

@ -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;
}
}

@ -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;
}
}

@ -14,12 +14,15 @@ 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() {
@ -38,6 +41,10 @@ public class ZNpcsModel {
return skinName; return skinName;
} }
public ZNpcsConversation getConversation() {
return conversation;
}
public ZNpcsLocation getLocation() { public ZNpcsLocation getLocation() {
return location; return location;
} }
@ -62,6 +69,10 @@ 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;
} }

@ -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;
}
}

@ -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 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,6 +10,7 @@ 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;
@ -72,6 +73,10 @@ 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();
@ -97,6 +102,11 @@ 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(line)); insertTextLineComponent(index, textSerializer.deserialize(textSerializer.serialize(MiniMessage.miniMessage().deserialize(line))));
} }
public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) { public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) {

@ -7,6 +7,7 @@ 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;
@ -70,6 +71,11 @@ 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,6 +1,7 @@
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;
@ -18,6 +19,7 @@ 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;
@ -146,9 +148,20 @@ 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) {
setProperty((EntityPropertyImpl<T>) 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<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) {
@ -201,4 +214,8 @@ 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"); addProperties("panda_rolling", "panda_sitting", "panda_on_back", "hand");
} else { } else {
addProperties("panda_eating"); addProperties("panda_eating");
} }

@ -23,4 +23,5 @@ 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_2PacketFactory extends V1_17PacketFactory { public class V1_19_3PacketFactory extends V1_17PacketFactory {
public V1_19_2PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) { public V1_19_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager); super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
} }
@ -30,7 +30,8 @@ public class V1_19_2PacketFactory 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, 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, 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,12 +17,13 @@ import org.bukkit.plugin.Plugin;
import java.util.Optional; 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; 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,7 +96,8 @@ 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(Component.text(" "), WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(
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);
}); });
@ -114,7 +115,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.empty(), Component.empty(), Component.empty(), Component.text(" "), null, null,
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()),
@ -174,4 +175,11 @@ 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<Skin> fetch(Player player); CompletableFuture<SkinImpl> fetch(Player player);
Skin fetchInstant(Player player); SkinImpl 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 Skin(properties)); return new PrefetchedDescriptor(new SkinImpl(properties));
} }
throw new IllegalArgumentException("Unknown SkinDescriptor type!"); throw new IllegalArgumentException("Unknown SkinDescriptor type!");
} }

@ -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 Skin(texture, signature)); return new PrefetchedDescriptor(new SkinImpl(texture, signature));
} }
@Override @Override

@ -6,6 +6,7 @@ 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;
@ -13,21 +14,21 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class Skin { public class SkinImpl implements 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 Skin(String texture, String signature) { public SkinImpl(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 Skin(Collection<TextureProperty> properties) { public SkinImpl(Collection<TextureProperty> properties) {
this.properties = new ArrayList<>(properties); this.properties = new ArrayList<>(properties);
} }
public Skin(Object propertyMap) { public SkinImpl(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);
@ -51,7 +52,7 @@ public class Skin {
} }
} }
public Skin(JsonObject obj) { public SkinImpl(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();
@ -71,4 +72,20 @@ public class 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;
}
} }

@ -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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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, Skin> cache = new ConcurrentHashMap<>(); private final Map<String, SkinImpl> 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, Skin> entry : cache.entrySet()) if (entry.getValue().isExpired()) cache.remove(entry.getKey()); for (Map.Entry<String, SkinImpl> 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<Skin> fetchByName(String name) { public CompletableFuture<SkinImpl> 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));
Skin skin = fetchByUUID(id).join(); SkinImpl 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<Skin> fetchByNameFallback(String name) { public CompletableFuture<SkinImpl> 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();
Skin skin = new Skin(value, signature); SkinImpl skin = new SkinImpl(value, signature);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -105,7 +105,7 @@ public class MojangSkinCache {
}); });
} }
public CompletableFuture<Skin> fetchByUrl(URL url, String variant) { public CompletableFuture<SkinImpl> 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 Skin(texture.get("value").getAsString(), texture.get("signature").getAsString()); return new SkinImpl(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;
Skin skin = cache.get(id.getId()); SkinImpl skin = cache.get(id.getId());
return !skin.isExpired(); return !skin.isExpired();
} }
public Skin getFullyCachedByName(String s) { public SkinImpl 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;
Skin skin = cache.get(id.getId()); SkinImpl skin = cache.get(id.getId());
if (skin.isExpired()) return null; if (skin.isExpired()) return null;
return skin; return skin;
} }
public CompletableFuture<Skin> fetchByUUID(String uuid) { public CompletableFuture<SkinImpl> 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)) {
Skin skin = cache.get(uuid); SkinImpl 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;
Skin skin = new Skin(obj); SkinImpl skin = new SkinImpl(obj);
cache.put(uuid, skin); cache.put(uuid, skin);
return skin; return skin;
} }
@ -195,12 +195,12 @@ public class MojangSkinCache {
}); });
} }
public Skin getFromPlayer(Player player) { public SkinImpl 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 Skin(propertyMap); return new SkinImpl(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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return skinCache.fetchByName(PapiUtil.set(player, name)); return skinCache.fetchByName(PapiUtil.set(player, name));
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl 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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skinCache.getFromPlayer(player)); return CompletableFuture.completedFuture(skinCache.getFromPlayer(player));
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl 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.Skin; import lol.pyr.znpcsplus.skin.SkinImpl;
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 Skin skin; private final SkinImpl skin;
public PrefetchedDescriptor(Skin skin) { public PrefetchedDescriptor(SkinImpl skin) {
this.skin = skin; this.skin = skin;
} }
@ -26,12 +26,12 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
} }
@Override @Override
public CompletableFuture<Skin> fetch(Player player) { public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skin); return CompletableFuture.completedFuture(skin);
} }
@Override @Override
public Skin fetchInstant(Player player) { public SkinImpl fetchInstant(Player player) {
return skin; return skin;
} }
@ -40,7 +40,7 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
return true; return true;
} }
public Skin getSkin() { public SkinImpl getSkin() {
return skin; return skin;
} }

@ -28,10 +28,7 @@ public class UpdateChecker extends BukkitRunnable {
if (resource == null) return; if (resource == null) return;
newestVersion = resource.getVersion(); newestVersion = resource.getVersion();
int current = versionToNumber(info.getVersion()); status = compareVersions(info.getVersion(), newestVersion);
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();
} }
@ -40,10 +37,43 @@ public class UpdateChecker extends BukkitRunnable {
logger.warning("Download it at " + UpdateChecker.DOWNLOAD_LINK); logger.warning("Download it at " + UpdateChecker.DOWNLOAD_LINK);
} }
private int versionToNumber(String version) { private Status compareVersions(String currentVersion, String newVersion) {
int num = Integer.parseInt(version.replaceAll("[^0-9]", "")); if (currentVersion.equalsIgnoreCase(newVersion)) return Status.LATEST_VERSION;
if (version.toLowerCase().contains("snapshot")) num -= 1; ReleaseType currentType = parseReleaseType(currentVersion);
return num; 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() { public Status getStatus() {
@ -57,4 +87,8 @@ 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
}
} }

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

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

@ -0,0 +1,3 @@
<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

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

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

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

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

@ -0,0 +1,3 @@
<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

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

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

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

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

@ -1,6 +1,3 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc teleport <gold><id>
<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,6 +1,3 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc toggle <gold><id>
<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,6 +1,3 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc type <gold><id> <type>
<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,6 +1,8 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc storage import <gold><importer>
<gold>* <yellow>/npc storage import <gold>znpcs
<gold>* <yellow>/npc storage import <gold>znpcsplus_legacy
<gold>* <yellow>/npc storage import <gold>citizens
<gray>Command used to import npcs from a different npc plugin <gray>Importers:
<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,5 +1,4 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc storage reload
<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,4 +1,3 @@
<gray>Examples: <gray>Usage <gold>» <yellow>/npc storage save
<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