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?
- 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/))

@ -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 {
*/
<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
*
* @return List of property keys
* @return Set of property keys
*/
Set<EntityProperty<?>> getAppliedProperties();
}

@ -124,4 +124,10 @@ public interface Npc extends PropertyHolder {
* @return The set of players that can currently see this npc
*/
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;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public interface SkinDescriptor {
CompletableFuture<? extends Skin> fetch(Player player);
Skin fetchInstant(Player player);
boolean supportsInstant(Player player);
}

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

@ -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")

@ -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<NpcEntryImpl> 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<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_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();

@ -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("..", ".");

@ -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 <id> <x> <y> <z>");
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<String> 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);
}
}

@ -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 <id> <yaw> <pitch>");
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<String> 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);
}
}

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

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

@ -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<NpcEntryImpl> 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<String, Object> toggleValues = model.getNpcToggleValues();
Map<String, Object> 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);

@ -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 glowName;
private ZNpcsConversation conversation;
private ZNpcsLocation location;
private String npcType;
private List<String> hologramLines;
private List<ZNpcsAction> clickActions;
private Map<String, String> npcEquip;
private Map<String, Object> npcToggleValues;
private Map<String, Object> npcFunctions;
private Map<String, String[]> 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<String, Object> getNpcFunctions() {
return npcFunctions;
}
public Map<String, String[]> getCustomizationMap() {
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
// 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
*/
}

@ -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<EntityProperty<?>> getAppliedProperties() {
return properties.getAppliedProperties();

@ -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) {

@ -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<M> 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<EntityProperty<?>> getAppliedProperties() {
return properties;

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

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

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

@ -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<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
public class V1_19_3PacketFactory extends V1_17PacketFactory {
public V1_19_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> 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<Void> 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);

@ -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<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
this.configManager = configManager;
}
@Override

@ -96,7 +96,8 @@ public class V1_8PacketFactory implements PacketFactory {
CompletableFuture<Void> 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<Integer, EntityData> 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));
}
}

@ -13,8 +13,8 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor extends SkinDescriptor {
CompletableFuture<Skin> fetch(Player player);
Skin fetchInstant(Player player);
CompletableFuture<SkinImpl> 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!");
}

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

@ -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<TextureProperty> 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<TextureProperty> properties) {
public SkinImpl(Collection<TextureProperty> 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;
}
}

@ -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<String, Skin> cache = new ConcurrentHashMap<>();
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
public MojangSkinCache(ConfigManager configManager) {
@ -32,11 +32,11 @@ public class MojangSkinCache {
}
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());
}
public CompletableFuture<Skin> fetchByName(String name) {
public CompletableFuture<SkinImpl> 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<Skin> fetchByNameFallback(String name) {
public CompletableFuture<SkinImpl> 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<Skin> fetchByUrl(URL url, String variant) {
public CompletableFuture<SkinImpl> 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<Skin> fetchByUUID(String uuid) {
public CompletableFuture<SkinImpl> 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);
}

@ -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<Skin> fetch(Player player) {
public CompletableFuture<SkinImpl> 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));
}

@ -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<Skin> fetch(Player player) {
public CompletableFuture<SkinImpl> fetch(Player player) {
return CompletableFuture.completedFuture(skinCache.getFromPlayer(player));
}
@Override
public Skin fetchInstant(Player player) {
public SkinImpl fetchInstant(Player player) {
return skinCache.getFromPlayer(player);
}

@ -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<Skin> fetch(Player player) {
public CompletableFuture<SkinImpl> 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;
}

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

@ -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:
<gold>* <yellow>/npc teleport <gold>cool_npc1
<gold>* <yellow>/npc teleport <gold>my_npc
<gold>* <yellow>/npc teleport <gold>12
<gray>Usage <gold>» <yellow>/npc teleport <gold><id>
<gray>Command used to teleport yourself to an npc

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

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

@ -1,6 +1,8 @@
<gray>Examples:
<gold>* <yellow>/npc storage import <gold>znpcs
<gold>* <yellow>/npc storage import <gold>znpcsplus_legacy
<gold>* <yellow>/npc storage import <gold>citizens
<gray>Usage <gold>» <yellow>/npc storage import <gold><importer>
<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:
<gold>* <yellow>/npc storage reload
<gray>Usage <gold>» <yellow>/npc storage reload
<gray>Command used to re-load all npcs from storage
<red>Warning: This command will delete all unsaved changes to npcs

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