├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── bukkit ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── github │ │ └── gonalez │ │ └── znpcs │ │ ├── ServersNPC.java │ │ ├── UnexpectedCallException.java │ │ ├── ZNPConfigSaveTask.java │ │ ├── ZNPConfigUtils.java │ │ ├── cache │ │ ├── CacheCategory.java │ │ ├── CachePackage.java │ │ ├── CacheRegistry.java │ │ └── TypeCache.java │ │ ├── commands │ │ ├── Command.java │ │ ├── CommandExecuteException.java │ │ ├── CommandInformation.java │ │ ├── CommandInvoker.java │ │ ├── CommandNotFoundException.java │ │ ├── CommandPermissionException.java │ │ ├── CommandSenderUtil.java │ │ ├── NPCreateCommand.java │ │ └── list │ │ │ ├── DefaultCommand.java │ │ │ └── inventory │ │ │ └── ConversationGUI.java │ │ ├── configuration │ │ ├── ConfigConfiguration.java │ │ ├── ConversationsConfiguration.java │ │ ├── DataConfiguration.java │ │ └── MessagesConfiguration.java │ │ ├── listeners │ │ ├── InventoryListener.java │ │ └── PlayerListener.java │ │ ├── npc │ │ ├── CustomizationLoader.java │ │ ├── FunctionContext.java │ │ ├── FunctionFactory.java │ │ ├── ItemSlot.java │ │ ├── NPC.java │ │ ├── NPCAction.java │ │ ├── NPCFunction.java │ │ ├── NPCModel.java │ │ ├── NPCPath.java │ │ ├── NPCSkin.java │ │ ├── NPCType.java │ │ ├── NamingType.java │ │ ├── TypeProperty.java │ │ ├── conversation │ │ │ ├── Conversation.java │ │ │ ├── ConversationKey.java │ │ │ ├── ConversationModel.java │ │ │ └── ConversationProcessor.java │ │ ├── event │ │ │ ├── ClickType.java │ │ │ └── NPCInteractEvent.java │ │ ├── function │ │ │ └── GlowFunction.java │ │ ├── hologram │ │ │ ├── Hologram.java │ │ │ └── replacer │ │ │ │ ├── LineReplacer.java │ │ │ │ └── RGBLine.java │ │ ├── packet │ │ │ ├── Packet.java │ │ │ ├── PacketCache.java │ │ │ ├── PacketFactory.java │ │ │ ├── PacketV16.java │ │ │ ├── PacketV17.java │ │ │ ├── PacketV18.java │ │ │ ├── PacketV19.java │ │ │ ├── PacketV8.java │ │ │ ├── PacketV9.java │ │ │ ├── PacketValue.java │ │ │ └── ValueType.java │ │ └── task │ │ │ ├── NPCLoadTask.java │ │ │ ├── NPCManagerTask.java │ │ │ └── NpcRefreshSkinTask.java │ │ ├── skin │ │ └── ApplySkinFetcherListener.java │ │ ├── user │ │ ├── EventService.java │ │ ├── NpcInteractServerHandler.java │ │ └── ZUser.java │ │ └── utility │ │ ├── BungeeUtils.java │ │ ├── GuavaCollectors.java │ │ ├── MetricsLite.java │ │ ├── PlaceholderUtils.java │ │ ├── ReflectionUtils.java │ │ ├── SchedulerUtils.java │ │ ├── Utils.java │ │ ├── inventory │ │ ├── ZInventory.java │ │ ├── ZInventoryCallback.java │ │ ├── ZInventoryHolder.java │ │ ├── ZInventoryItem.java │ │ └── ZInventoryPage.java │ │ ├── itemstack │ │ ├── ItemStackBuilder.java │ │ └── ItemStackSerializer.java │ │ └── location │ │ └── ZLocation.java │ └── resources │ └── plugin.yml ├── core ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── gonalez │ │ └── znpcs │ │ ├── command │ │ ├── Command.java │ │ ├── CommandProvider.java │ │ ├── CommandResult.java │ │ └── SimpleCommandProvider.java │ │ ├── configuration │ │ ├── Configuration.java │ │ ├── ConfigurationIndex.java │ │ ├── ConfigurationKey.java │ │ ├── GsonConfigurationIndex.java │ │ ├── PathConfigurationIndex.java │ │ ├── WritableConfigurationIndex.java │ │ └── YamlConfigurationIndex.java │ │ └── skin │ │ ├── AbstractSkinFetcherServer.java │ │ ├── AshconSkinFetcherServer.java │ │ ├── GameProfiles.java │ │ ├── MineSkinFetcher.java │ │ ├── SkinException.java │ │ ├── SkinFetcher.java │ │ ├── SkinFetcherImpl.java │ │ ├── SkinFetcherListener.java │ │ └── SkinFetcherServer.java │ └── test │ └── java │ └── io │ └── github │ └── gonalez │ └── znpcs │ ├── commands │ └── CommandTest.java │ ├── configuration │ └── ConfigurationTest.java │ └── skin │ └── SkinFetcherTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | 4 | .gradle 5 | 6 | .idea 7 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [spigot-link]: https://www.spigotmc.org/resources/znpcs.80940 2 | [wiki-link]: https://github.com/gonalez/znpcs/wiki 3 | 4 | # ZNPCs 5 | 6 | ZNPCs is a [minecraft plugin][spigot-link] to create NPCs (non-player entities). 7 | 8 | ### Learn more 9 | 10 | See the [wiki][wiki-link] for documentation & usage examples. 11 | 12 | ## License 13 | 14 | Copyright 2024 - Gaston Gonzalez (Gonalez). 15 | 16 | Licensed under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | apply plugin: "java-library" 3 | 4 | group = project.property("GROUP_ID") 5 | version = project.property("VERSION_NAME") 6 | 7 | repositories { 8 | gradlePluginPortal() 9 | mavenCentral() 10 | mavenLocal() 11 | } 12 | 13 | dependencies { 14 | testImplementation("org.junit.jupiter:junit-jupiter:5.11.0") 15 | testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") 16 | testImplementation("com.google.truth:truth:1.4.1") 17 | } 18 | 19 | test { 20 | useJUnitPlatform() 21 | } 22 | 23 | tasks.withType(JavaCompile) { 24 | sourceCompatibility = JavaVersion.VERSION_11 25 | targetCompatibility = JavaVersion.VERSION_11 26 | 27 | options.encoding = "UTF-8" 28 | } 29 | } -------------------------------------------------------------------------------- /bukkit/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.johnrengelman.shadow' version '7.1.2' 3 | } 4 | 5 | repositories { 6 | maven { url = "https://libraries.minecraft.net/" } 7 | maven { url = "https://repo.helpch.at/releases/" } 8 | maven { url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" } 9 | } 10 | 11 | dependencies { 12 | implementation project(":core") 13 | compileOnly("io.netty:netty-all:4.1.77.Final") 14 | compileOnly("org.spigotmc:spigot-api:1.16.2-R0.1-SNAPSHOT") 15 | compileOnly("me.clip:placeholderapi:2.11.5") 16 | } 17 | 18 | shadowJar { 19 | dependencies { 20 | include(dependency("io.github.gonalez.znpcs:.*")) 21 | } 22 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/ServersNPC.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import io.github.gonalez.znpcs.ZNPConfigUtils.PluginConfigConfigurationFormat; 6 | import io.github.gonalez.znpcs.commands.list.DefaultCommand; 7 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 8 | import io.github.gonalez.znpcs.configuration.DataConfiguration; 9 | import io.github.gonalez.znpcs.listeners.InventoryListener; 10 | import io.github.gonalez.znpcs.listeners.PlayerListener; 11 | import io.github.gonalez.znpcs.npc.NPC; 12 | import io.github.gonalez.znpcs.npc.NPCModel; 13 | import io.github.gonalez.znpcs.npc.NPCPath; 14 | import io.github.gonalez.znpcs.npc.NPCType; 15 | import io.github.gonalez.znpcs.npc.task.NPCManagerTask; 16 | import io.github.gonalez.znpcs.npc.task.NpcRefreshSkinTask; 17 | import io.github.gonalez.znpcs.skin.AshconSkinFetcherServer; 18 | import io.github.gonalez.znpcs.skin.MineSkinFetcher; 19 | import io.github.gonalez.znpcs.skin.SkinFetcher; 20 | import io.github.gonalez.znpcs.skin.SkinFetcherImpl; 21 | import io.github.gonalez.znpcs.user.ZUser; 22 | import io.github.gonalez.znpcs.utility.BungeeUtils; 23 | import io.github.gonalez.znpcs.utility.MetricsLite; 24 | import io.github.gonalez.znpcs.utility.SchedulerUtils; 25 | import io.github.gonalez.znpcs.utility.itemstack.ItemStackSerializer; 26 | import io.github.gonalez.znpcs.utility.location.ZLocation; 27 | import java.util.concurrent.Executors; 28 | import org.bukkit.Bukkit; 29 | import org.bukkit.Location; 30 | import org.bukkit.inventory.ItemStack; 31 | import org.bukkit.plugin.java.JavaPlugin; 32 | 33 | import java.io.File; 34 | import java.io.IOException; 35 | import java.nio.file.FileVisitResult; 36 | import java.nio.file.Files; 37 | import java.nio.file.Path; 38 | import java.nio.file.SimpleFileVisitor; 39 | import java.nio.file.attribute.BasicFileAttributes; 40 | import java.util.Collections; 41 | import java.util.logging.Level; 42 | 43 | public class ServersNPC extends JavaPlugin { 44 | public static final String PATH_EXTENSION = ".path"; 45 | 46 | public static final Gson GSON = 47 | (new GsonBuilder()) 48 | .registerTypeAdapter(ZLocation.class, ZLocation.SERIALIZER) 49 | .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackSerializer()) 50 | .setPrettyPrinting() 51 | .disableHtmlEscaping() 52 | .create(); 53 | 54 | public static SchedulerUtils SCHEDULER; 55 | 56 | public static BungeeUtils BUNGEE_UTILS; 57 | 58 | private ZNPConfigSaveTask configSaveTask; 59 | 60 | @Override 61 | public void onEnable() { 62 | Path pluginPath = getDataFolder().toPath(); 63 | 64 | Path pathPath = pluginPath.resolve("paths"); 65 | try { 66 | loadAllPaths(pathPath); 67 | } catch (IOException e) { 68 | getLogger().log(Level.WARNING, "Could not load paths", e); 69 | } 70 | 71 | getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); 72 | new MetricsLite(this, 8054); 73 | 74 | ZNPConfigUtils.setConfigurationManager(new PluginConfigConfigurationFormat(pluginPath, GSON)); 75 | 76 | SkinFetcher skinFetcher = 77 | SkinFetcherImpl.builder() 78 | .setSkinExecutor(Executors.newSingleThreadExecutor()) 79 | .addSkinFetcherServer(new AshconSkinFetcherServer(), new MineSkinFetcher()) 80 | .build(); 81 | new DefaultCommand(pathPath, skinFetcher); 82 | 83 | SCHEDULER = new SchedulerUtils(this); 84 | BUNGEE_UTILS = new BungeeUtils(this); 85 | 86 | Bukkit.getOnlinePlayers().forEach(ZUser::find); 87 | 88 | new NPCManagerTask(this); 89 | (configSaveTask = new ZNPConfigSaveTask()).runTaskTimerAsynchronously(this, 300, 90 | ZNPConfigUtils.getConfig(ConfigConfiguration.class).saveNpcsDelaySeconds); 91 | new NpcRefreshSkinTask(skinFetcher).runTaskTimerAsynchronously(this, 0L, 20L); 92 | 93 | new PlayerListener(this); 94 | new InventoryListener(this); 95 | } 96 | 97 | @Override 98 | public void onDisable() { 99 | Bukkit.getOnlinePlayers().forEach(ZUser::unregister); 100 | if (configSaveTask != null) { 101 | configSaveTask.run(); 102 | } 103 | } 104 | 105 | /** 106 | * Finds all files that qualify as NPC paths. A file is considered a valid NPC path file 107 | * if its name ends with {@link #PATH_EXTENSION}. This method reads each qualifying file 108 | * and converts it to an NPC path & initializes it. 109 | */ 110 | private void loadAllPaths(Path directory) throws IOException { 111 | if (Files.isDirectory(directory)) { 112 | Files.walkFileTree(directory, new SimpleFileVisitor() { 113 | @Override 114 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 115 | if (!Files.isDirectory(file) 116 | && file.getFileName().toString().endsWith(PATH_EXTENSION)) { 117 | loadPath(file.toFile()); 118 | } 119 | return FileVisitResult.CONTINUE; 120 | } 121 | 122 | void loadPath(File file) { 123 | NPCPath.AbstractTypeWriter abstractTypeWriter = 124 | NPCPath.AbstractTypeWriter.forFile( 125 | file, NPCPath.AbstractTypeWriter.TypeWriter.MOVEMENT); 126 | abstractTypeWriter.load(); 127 | } 128 | }); 129 | } 130 | Files.createDirectories(directory); 131 | } 132 | 133 | public static NPC createNPC(int id, NPCType npcType, Location location, String name) { 134 | NPC find = NPC.find(id); 135 | if (find != null) return find; 136 | NPCModel pojo = 137 | (new NPCModel(id)) 138 | .withHologramLines(Collections.singletonList(name)) 139 | .withLocation(new ZLocation(location)) 140 | .withNpcType(npcType); 141 | // TODO: Make a proper npc saving 142 | ZNPConfigUtils.getConfig(DataConfiguration.class).npcList.add(pojo); 143 | return new NPC(pojo, true); 144 | } 145 | 146 | public static void deleteNPC(int npcID) { 147 | NPC npc = NPC.find(npcID); 148 | if (npc == null) 149 | throw new IllegalStateException("can't find npc: " + npcID); 150 | NPC.unregister(npcID); 151 | // TODO: Make a proper npc saving 152 | ZNPConfigUtils.getConfig(DataConfiguration.class).npcList.remove(npc.getNpcPojo()); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/UnexpectedCallException.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs; 2 | 3 | public class UnexpectedCallException extends RuntimeException { 4 | public UnexpectedCallException(Throwable cause) { 5 | super(cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/ZNPConfigSaveTask.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs; 2 | 3 | import static com.google.common.base.Predicates.alwaysTrue; 4 | 5 | import org.bukkit.scheduler.BukkitRunnable; 6 | 7 | class ZNPConfigSaveTask extends BukkitRunnable { 8 | 9 | @Override 10 | public void run() { 11 | ZNPConfigUtils.rewriteConfigs(alwaysTrue()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/ZNPConfigUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Predicate; 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.gson.Gson; 7 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 8 | import io.github.gonalez.znpcs.configuration.Configuration; 9 | import io.github.gonalez.znpcs.configuration.ConfigurationManager; 10 | import io.github.gonalez.znpcs.configuration.ConversationsConfiguration; 11 | import io.github.gonalez.znpcs.configuration.DataConfiguration; 12 | import io.github.gonalez.znpcs.configuration.GsonConfigurationManager; 13 | import io.github.gonalez.znpcs.configuration.MessagesConfiguration; 14 | import java.nio.file.Path; 15 | import java.util.LinkedHashMap; 16 | import java.util.Map; 17 | import java.util.concurrent.atomic.AtomicReference; 18 | import javax.annotation.Nullable; 19 | 20 | public final class ZNPConfigUtils { 21 | 22 | static final ImmutableMap, String> PLUGIN_CONFIGURATIONS = 23 | ImmutableMap.of( 24 | ConfigConfiguration.class, "config", 25 | MessagesConfiguration.class, "messages", 26 | DataConfiguration.class, "data", 27 | ConversationsConfiguration.class, "conversations"); 28 | 29 | private static final AtomicReference CONFIG_MANAGER_REF = new AtomicReference<>(null); 30 | 31 | static final Map, Configuration> knownConfigs = new LinkedHashMap<>(); 32 | 33 | private ZNPConfigUtils() {} 34 | 35 | private static void setupConfigs(ConfigurationManager configurationManager) { 36 | for (Class configType : PLUGIN_CONFIGURATIONS.keySet()) { 37 | Configuration configuration = configurationManager.createConfiguration( 38 | configType, configurationManager.createDefaultWriter()); 39 | knownConfigs.put(configType, configuration); 40 | } 41 | } 42 | 43 | static void setConfigurationManager(ConfigurationManager configurationManager) { 44 | CONFIG_MANAGER_REF.set(configurationManager); 45 | setupConfigs(configurationManager); 46 | } 47 | 48 | public static void rewriteConfigs(Predicate shouldSavePredicate) { 49 | ConfigurationManager configurationManager = CONFIG_MANAGER_REF.get(); 50 | for (Configuration configuration : knownConfigs.values()) { 51 | if (shouldSavePredicate.apply(configuration)) { 52 | configurationManager.writeConfig(configuration, configurationManager.createDefaultWriter()); 53 | } 54 | } 55 | } 56 | 57 | @SuppressWarnings("unchecked") 58 | public static T getConfig(Class configType) { 59 | if (knownConfigs.containsKey(configType)) { 60 | return (T) knownConfigs.get(configType); 61 | } 62 | throw new NullPointerException("Not a plugin config: " + configType); 63 | } 64 | 65 | static class PluginConfigConfigurationFormat extends GsonConfigurationManager { 66 | private final Path pluginFolder; 67 | 68 | public PluginConfigConfigurationFormat(Path pluginFolder, Gson gson) { 69 | super(gson); 70 | this.pluginFolder = Preconditions.checkNotNull(pluginFolder); 71 | } 72 | 73 | @Override 74 | public void setPath(Class configurationClass, Path path) { 75 | throw new UnsupportedOperationException("plugin only"); 76 | } 77 | 78 | @Nullable 79 | @Override 80 | public Path getPath(Class configurationClass) { 81 | if (PLUGIN_CONFIGURATIONS.containsKey(configurationClass)) { 82 | return pluginFolder.resolve(PLUGIN_CONFIGURATIONS.get(configurationClass) + ".json"); 83 | } 84 | return null; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/cache/CacheCategory.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.cache; 2 | 3 | public enum CacheCategory { 4 | DEFAULT(""), 5 | NETWORK("network"), 6 | PROTOCOL("network.protocol"), 7 | CHAT("network.chat"), 8 | PACKET("network.protocol.game"), 9 | SYNCHER("network.syncher"), 10 | ENTITY("world.entity"), 11 | WORLD_ENTITY_PLAYER("world.entity.player"), 12 | ITEM("world.item"), 13 | WORLD_LEVEL("world.level"), 14 | WORLD_SCORES("world.scores"), 15 | SERVER_LEVEL("server.level"), 16 | SERVER_NETWORK("server.network"), 17 | SERVER("server"); 18 | 19 | private static final String EMPTY_STRING = ""; 20 | 21 | private final String subPackageName; 22 | 23 | private final String packageName; 24 | 25 | CacheCategory(String subPackageName) { 26 | this.subPackageName = subPackageName; 27 | StringBuilder stringBuilder = new StringBuilder(CachePackage.MINECRAFT_SERVER.getFixedPackageName()); 28 | if (subPackageName.length() > 0) { 29 | stringBuilder.append("."); 30 | stringBuilder.append(subPackageName); 31 | } 32 | this.packageName = stringBuilder.toString(); 33 | } 34 | 35 | public String getSubPackageName() { 36 | return this.subPackageName; 37 | } 38 | 39 | public String getPackageName() { 40 | return this.packageName; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/cache/CachePackage.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.cache; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | 5 | public enum CachePackage { 6 | DEFAULT, 7 | CRAFT_BUKKIT("org.bukkit.craftbukkit." + Utils.getBukkitPackage()), 8 | MINECRAFT_SERVER("net.minecraft"); 9 | 10 | private final String fixedPackageName; 11 | 12 | CachePackage(String packageName) { 13 | this 14 | 15 | .fixedPackageName = Utils.versionNewer(17) ? packageName : (packageName + (packageName.contains("minecraft") ? (".server." + Utils.getBukkitPackage()) : "")); 16 | } 17 | 18 | CachePackage() { 19 | this.fixedPackageName = ""; 20 | } 21 | 22 | public String getForCategory(CacheCategory packetCategory, String extra) { 23 | return Utils.versionNewer(17) ? ( 24 | packetCategory.getPackageName() + ((extra.length() > 0) ? ("." + extra) : "")) : 25 | this.fixedPackageName; 26 | } 27 | 28 | public String getFixedPackageName() { 29 | return this.fixedPackageName; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/Command.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Iterables; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.ChatColor; 8 | import org.bukkit.command.CommandMap; 9 | import org.bukkit.command.CommandSender; 10 | import org.bukkit.command.defaults.BukkitCommand; 11 | 12 | import java.lang.reflect.Method; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.Set; 17 | 18 | public class Command extends BukkitCommand { 19 | private static final String WHITESPACE = " "; 20 | 21 | private static final CommandMap COMMAND_MAP; 22 | 23 | private final Map subCommands; 24 | 25 | static { 26 | try { 27 | COMMAND_MAP = (CommandMap) CacheRegistry.BUKKIT_COMMAND_MAP.load().get(Bukkit.getServer()); 28 | } catch (IllegalAccessException exception) { 29 | throw new IllegalStateException("can't access bukkit command map."); 30 | } 31 | } 32 | 33 | public Command(String name) { 34 | super(name); 35 | this.subCommands = new HashMap<>(); 36 | load(); 37 | } 38 | 39 | private void load() { 40 | COMMAND_MAP.register(getName(), this); 41 | for (Method method : getClass().getMethods()) { 42 | if (method.isAnnotationPresent(CommandInformation.class)) { 43 | CommandInformation cmdInfo = method.getAnnotation(CommandInformation.class); 44 | this.subCommands.put(cmdInfo, new CommandInvoker(this, method, cmdInfo.permission())); 45 | } 46 | } 47 | } 48 | 49 | public Set getCommands() { 50 | return this.subCommands.keySet(); 51 | } 52 | 53 | public boolean execute(CommandSender sender, String commandLabel, String[] args) { 54 | Optional> subCommandOptional = this.subCommands.entrySet() 55 | .stream().filter(command -> 56 | command.getKey().name().contentEquals((args.length > 0) ? args[0] : "")).findFirst(); 57 | if (!subCommandOptional.isPresent()) { 58 | sender.sendMessage(ChatColor.RED + "Unknown subcommand for arguments."); 59 | return false; 60 | } 61 | try { 62 | ImmutableList list = ImmutableList.copyOf(args); 63 | 64 | Map.Entry subCommand = subCommandOptional.get(); 65 | subCommand.getValue().execute( 66 | sender, 67 | ImmutableList.copyOf(Iterables.skip(list, 1))); 68 | } catch (CommandExecuteException e) { 69 | sender.sendMessage(ChatColor.RED + "Failed to execute command."); 70 | e.printStackTrace(); 71 | } catch (CommandPermissionException e) { 72 | sender.sendMessage(ChatColor.RED + "No permission."); 73 | } 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandExecuteException.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | public class CommandExecuteException extends Exception { 4 | public CommandExecuteException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandInformation.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Target({ElementType.METHOD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface CommandInformation { 13 | String[] arguments(); 14 | 15 | String[] help() default {}; 16 | 17 | String name(); 18 | 19 | String permission(); 20 | 21 | boolean isMultiple() default false; 22 | } 23 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandInvoker.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | public class CommandInvoker { 8 | private final Command command; 9 | 10 | private final Method commandMethod; 11 | 12 | private final String permission; 13 | 14 | public CommandInvoker(Command command, Method commandMethod, String permission) { 15 | this.command = command; 16 | this.commandMethod = commandMethod; 17 | this.permission = permission; 18 | } 19 | 20 | public void execute(CommandSender sender, Object command) throws CommandPermissionException, CommandExecuteException { 21 | if (this.permission.length() > 0 && !sender.hasPermission(this.permission)) 22 | throw new CommandPermissionException("Insufficient permission."); 23 | try { 24 | this.commandMethod.invoke(this.command, sender, command); 25 | } catch (IllegalAccessException|java.lang.reflect.InvocationTargetException e) { 26 | throw new CommandExecuteException(e.getMessage(), e.getCause()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandNotFoundException.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | public class CommandNotFoundException extends Exception { 4 | public CommandNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandPermissionException.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | public class CommandPermissionException extends Exception { 4 | public CommandPermissionException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/CommandSenderUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.Iterables; 6 | import io.github.gonalez.znpcs.utility.Utils; 7 | import net.md_5.bungee.api.chat.ComponentBuilder; 8 | import net.md_5.bungee.api.chat.HoverEvent; 9 | import net.md_5.bungee.api.chat.TextComponent; 10 | import org.bukkit.command.CommandSender; 11 | import org.bukkit.entity.Player; 12 | 13 | import java.util.Arrays; 14 | import java.util.stream.Collectors; 15 | 16 | public final class CommandSenderUtil { 17 | private static final Joiner LINE_SEPARATOR_JOINER = Joiner.on("\n"); 18 | private static final ImmutableList HELP_PREFIX = ImmutableList.of("&6&lEXAMPLES&r:"); 19 | 20 | public static void sendMessage(CommandSender sender, CommandInformation subCommand) { 21 | sendMessage(sender, " &7» &6/&eznpcs " + subCommand.name() + " " + 22 | Arrays.stream(subCommand.arguments()) 23 | .map(s -> "<" + s + ">") 24 | .collect(Collectors.joining(" ")), 25 | Arrays.asList(subCommand.help())); 26 | } 27 | 28 | public static void sendMessage( 29 | CommandSender sender, String message, Iterable hover) { 30 | message = Utils.toColor(message); 31 | if (sender instanceof Player) { 32 | TextComponent textComponent = new TextComponent(TextComponent.fromLegacyText(Utils.toColor(message))); 33 | if (hover != null) 34 | textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, (new ComponentBuilder( 35 | Utils.toColor(LINE_SEPARATOR_JOINER 36 | .join(Iterables.concat(HELP_PREFIX, hover))))) 37 | .create())); 38 | ((Player)sender).spigot().sendMessage(textComponent); 39 | } else { 40 | sender.sendMessage(message); 41 | } 42 | } 43 | 44 | private CommandSenderUtil() {} 45 | } 46 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/commands/NPCreateCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import static io.github.gonalez.znpcs.ZNPConfigUtils.getConfig; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.common.primitives.Ints; 7 | import io.github.gonalez.znpcs.ServersNPC; 8 | import io.github.gonalez.znpcs.command.Command; 9 | import io.github.gonalez.znpcs.command.CommandProvider; 10 | import io.github.gonalez.znpcs.command.CommandResult; 11 | import io.github.gonalez.znpcs.configuration.DataConfiguration; 12 | import io.github.gonalez.znpcs.configuration.MessagesConfiguration; 13 | import io.github.gonalez.znpcs.npc.NPC; 14 | import io.github.gonalez.znpcs.npc.NPCType; 15 | 16 | public class NPCreateCommand extends Command { 17 | 18 | @Override 19 | public String getName() { 20 | return "create"; 21 | } 22 | 23 | @Override 24 | protected int getMandatoryArguments() { 25 | return 3; 26 | } 27 | 28 | @Override 29 | protected CommandResult execute(CommandProvider commandProvider, ImmutableList args) { 30 | Integer id = Ints.tryParse(args.get(0)); 31 | if (id == null) { 32 | return newCommandResult().setErrorMessage(getConfig(MessagesConfiguration.class).invalidNumber); 33 | } 34 | if (getConfig(DataConfiguration.class).npcList.stream().anyMatch(npc -> (npc.getId() == id))) { 35 | return newCommandResult().setErrorMessage(getConfig(MessagesConfiguration.class).npcFound); 36 | } 37 | NPCType npcType; 38 | try { 39 | npcType = NPCType.valueOf(args.get(1).toUpperCase()); 40 | } catch (IllegalArgumentException e) { 41 | return newCommandResult().setErrorMessage(getConfig(MessagesConfiguration.class).incorrectUsage); 42 | } 43 | String name = args.get(2).trim(); 44 | if (name.length() < 3 || name.length() > 16) { 45 | return newCommandResult().setErrorMessage(getConfig(MessagesConfiguration.class).invalidNumber); 46 | } 47 | NPC npc = ServersNPC.createNPC(id, npcType, null, name); 48 | if (npcType == NPCType.PLAYER) { 49 | //commandProvider.getCommand(NPCSkinCommand.class).execute(commandProvider, ImmutableList.of(String.valueOf(id), name)); 50 | } 51 | return newCommandResult().setSuccessMessage(getConfig(MessagesConfiguration.class).success); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/configuration/ConfigConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | public class ConfigConfiguration extends Configuration { 4 | 5 | @ConfigurationKey( 6 | name = "VIEW_DISTANCE", 7 | description = "View distance in blocks") 8 | public int viewDistance = 32; 9 | 10 | @ConfigurationKey( 11 | name = "REPLACE_SYMBOL", 12 | description = "Symbol for replacement") 13 | public String replaceSymbol = "-"; 14 | 15 | @ConfigurationKey( 16 | name = "SAVE_NPCS_DELAY_SECONDS", 17 | description = "Delay in seconds to save NPCs") 18 | public int saveNpcsDelaySeconds = 600; 19 | 20 | @ConfigurationKey( 21 | name = "MAX_PATH_LOCATIONS", 22 | description = "Maximum path locations") 23 | public int maxPathLocations = 500; 24 | 25 | @ConfigurationKey( 26 | name = "DEBUG_ENABLED", 27 | description = "Is debug enabled") 28 | public boolean debugEnabled = true; 29 | 30 | @ConfigurationKey( 31 | name = "LINE_SPACING", 32 | description = "Spacing between lines") 33 | public double lineSpacing = 0.3; 34 | 35 | @ConfigurationKey( 36 | name = "ANIMATION_RGB", 37 | description = "Is animation RGB enabled") 38 | public boolean animationRgb = false; 39 | } 40 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/configuration/ConversationsConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | import io.github.gonalez.znpcs.npc.conversation.Conversation; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class ConversationsConfiguration extends Configuration { 8 | 9 | @ConfigurationKey( 10 | name = "CONVERSATION_LIST", 11 | description = "List of conversations") 12 | public List conversationList = new ArrayList<>(); 13 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/configuration/DataConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | import io.github.gonalez.znpcs.npc.NPCModel; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class DataConfiguration extends Configuration { 8 | 9 | @ConfigurationKey( 10 | name = "NPC_LIST", 11 | description = "List of NPC models") 12 | public List npcList = new ArrayList<>(); 13 | } 14 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/configuration/MessagesConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | public class MessagesConfiguration extends Configuration { 4 | 5 | @ConfigurationKey( 6 | name = "NO_PERMISSION", 7 | description = "Message for no permission") 8 | public String noPermission = "&cYou do not have permission to execute this command."; 9 | 10 | @ConfigurationKey( 11 | name = "SUCCESS", 12 | description = "Message for.success") 13 | public String success = "&aDone..."; 14 | 15 | @ConfigurationKey( 16 | name = "INCORRECT_USAGE", 17 | description = "Message for incorrect command usage") 18 | public String incorrectUsage = "&cIncorrect use of command."; 19 | 20 | @ConfigurationKey( 21 | name = "COMMAND_NOT_FOUND", 22 | description = "Message for command not found") 23 | public String commandNotFound = "&cThis command was not found."; 24 | 25 | @ConfigurationKey( 26 | name = "COMMAND_ERROR", 27 | description = "Message for command error") 28 | public String commandError = "&cThere was an error executing the command, see the console for more information."; 29 | 30 | @ConfigurationKey( 31 | name = "INVALID_NUMBER", 32 | description = "Message for invalid number") 33 | public String invalidNumber = "&cHey!, The inserted number/id does not look like a number.."; 34 | 35 | @ConfigurationKey( 36 | name = "NPC_NOT_FOUND", 37 | description = "Message for NPC not found") 38 | public String npcNotFound = "&cHey!, I couldn't find a npc with this id."; 39 | 40 | @ConfigurationKey( 41 | name = "TOO_FEW_ARGUMENTS", 42 | description = "Message for too few arguments") 43 | public String tooFewArguments = "&cToo few arguments."; 44 | 45 | @ConfigurationKey( 46 | name = "PATH_START", 47 | description = "Message for path start") 48 | public String pathStart = "&aDone, now walk where you want the npc to, when u finish type /znpcs path exit."; 49 | 50 | @ConfigurationKey( 51 | name = "EXIT_PATH", 52 | description = "Message for exiting path creation") 53 | public String exitPath = "&cYou have exited the waypoint creation."; 54 | 55 | @ConfigurationKey( 56 | name = "PATH_FOUND", 57 | description = "Message for path already exists") 58 | public String pathFound = "&cThere is already a path with this name."; 59 | 60 | @ConfigurationKey( 61 | name = "NPC_FOUND", 62 | description = "Message for NPC already exists") 63 | public String npcFound = "&cThere is already a npc with this id."; 64 | 65 | @ConfigurationKey( 66 | name = "NO_PATH_FOUND", 67 | description = "Message for no path found") 68 | public String noPathFound = "&cNo path found."; 69 | 70 | @ConfigurationKey( 71 | name = "NO_SKIN_FOUND", 72 | description = "Message for no skin found") 73 | public String noSkinFound = "&cSkin not found."; 74 | 75 | @ConfigurationKey( 76 | name = "NO_NPC_FOUND", 77 | description = "Message for no NPC found") 78 | public String noNpcFound = "&cNo npc found."; 79 | 80 | @ConfigurationKey( 81 | name = "NO_ACTION_FOUND", 82 | description = "Message for no action found") 83 | public String noActionFound = "&cNo action found."; 84 | 85 | @ConfigurationKey( 86 | name = "NO_LINE_FOUND", 87 | description = "Message for no line found") 88 | public String noLineFound = "&cNo line found."; 89 | 90 | @ConfigurationKey( 91 | name = "METHOD_NOT_FOUND", 92 | description = "Message for method not found") 93 | public String methodNotFound = "&cNo method found."; 94 | 95 | @ConfigurationKey( 96 | name = "INVALID_NAME_LENGTH", 97 | description = "Message for invalid name length") 98 | public String invalidNameLength = "&cThe name is too short or long, it must be in the range of (3 to 16) characters."; 99 | 100 | @ConfigurationKey( 101 | name = "UNSUPPORTED_ENTITY", 102 | description = "Message for unsupported entity type") 103 | public String unsupportedEntity = "&cEntity type not available for your current version."; 104 | 105 | @ConfigurationKey( 106 | name = "PATH_SET_INCORRECT_USAGE", 107 | description = "Message for incorrect path set usage") 108 | public String pathSetIncorrectUsage = "&eUsage: &aset "; 109 | 110 | @ConfigurationKey( 111 | name = "ACTION_ADD_INCORRECT_USAGE", 112 | description = "Message for incorrect action add usage") 113 | public String actionAddIncorrectUsage = "&eUsage: &a "; 114 | 115 | @ConfigurationKey( 116 | name = "ACTION_DELAY_INCORRECT_USAGE", 117 | description = "Message for incorrect action delay usage") 118 | public String actionDelayIncorrectUsage = "&eUsage: &a "; 119 | 120 | @ConfigurationKey( 121 | name = "CONVERSATION_SET_INCORRECT_USAGE", 122 | description = "Message for incorrect conversation set usage") 123 | public String conversationSetIncorrectUsage = "&cUsage: "; 124 | 125 | @ConfigurationKey( 126 | name = "NO_CONVERSATION_FOUND", 127 | description = "Message for no conversation found") 128 | public String noConversationFound = "&cNo conversation found."; 129 | 130 | @ConfigurationKey( 131 | name = "CONVERSATION_FOUND", 132 | description = "Message for conversation already exists") 133 | public String conversationFound = "&cThere is already a conversation with this name."; 134 | 135 | @ConfigurationKey( 136 | name = "INVALID_SIZE", 137 | description = "Message for invalid size") 138 | public String invalidSize = "&cThe position cannot exceed the limit."; 139 | 140 | @ConfigurationKey( 141 | name = "FETCHING_SKIN", 142 | description = "Message for fetching skin") 143 | public String fetchingSkin = "&aFetching skin for name: &f%s&a, wait..."; 144 | 145 | @ConfigurationKey( 146 | name = "CANT_GET_SKIN", 147 | description = "Message for can't fetch skin") 148 | public String cantGetSkin = "&cCan't fetch skin with name: %s."; 149 | 150 | @ConfigurationKey( 151 | name = "GET_SKIN", 152 | description = "Message for skin fetched") 153 | public String getSkin = "&aSkin fetched."; 154 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/listeners/InventoryListener.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.listeners; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import io.github.gonalez.znpcs.utility.inventory.ZInventory; 5 | import io.github.gonalez.znpcs.utility.inventory.ZInventoryHolder; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.inventory.InventoryClickEvent; 10 | 11 | public class InventoryListener implements Listener { 12 | public InventoryListener(ServersNPC serversNPC) { 13 | serversNPC.getServer().getPluginManager().registerEvents(this, serversNPC); 14 | } 15 | 16 | @EventHandler 17 | public void onClick(InventoryClickEvent event) { 18 | if (!(event.getWhoClicked() instanceof Player)) 19 | return; 20 | if (event.getCurrentItem() == null) 21 | return; 22 | if (!(event.getInventory().getHolder() instanceof ZInventoryHolder)) 23 | return; 24 | event.setCancelled(true); 25 | ZInventory zInventory = ((ZInventoryHolder)event.getInventory().getHolder()).getzInventory(); 26 | if (!zInventory.getPage().containsItem(event.getRawSlot())) 27 | return; 28 | zInventory.getPage().findItem(event.getRawSlot()).getInventoryCallback().onClick(event); 29 | ((Player)event.getWhoClicked()).updateInventory(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/listeners/PlayerListener.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.listeners; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import io.github.gonalez.znpcs.npc.conversation.ConversationModel; 5 | import io.github.gonalez.znpcs.npc.event.NPCInteractEvent; 6 | import io.github.gonalez.znpcs.user.EventService; 7 | import io.github.gonalez.znpcs.user.ZUser; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.Listener; 10 | import org.bukkit.event.player.AsyncPlayerChatEvent; 11 | import org.bukkit.event.player.PlayerJoinEvent; 12 | import org.bukkit.event.player.PlayerQuitEvent; 13 | 14 | public class PlayerListener implements Listener { 15 | public PlayerListener(ServersNPC serversNPC) { 16 | serversNPC.getServer().getPluginManager().registerEvents(this, serversNPC); 17 | } 18 | 19 | @EventHandler 20 | public void onJoin(PlayerJoinEvent event) { 21 | ZUser.find(event.getPlayer()); 22 | } 23 | 24 | @EventHandler 25 | public void onQuit(PlayerQuitEvent event) { 26 | ZUser.unregister(event.getPlayer()); 27 | } 28 | 29 | @EventHandler(ignoreCancelled = true) 30 | public void onTalk(AsyncPlayerChatEvent event) { 31 | ZUser zUser = ZUser.find(event.getPlayer()); 32 | if (EventService.hasService(zUser, AsyncPlayerChatEvent.class)) { 33 | event.setCancelled(true); 34 | EventService eventService = EventService.findService(zUser, AsyncPlayerChatEvent.class); 35 | eventService.runAll(event); 36 | zUser.getEventServices().remove(eventService); 37 | } 38 | } 39 | 40 | @EventHandler 41 | public void onConversation(NPCInteractEvent event) { 42 | ConversationModel conversationStorage = event.getNpc().getNpcPojo().getConversation(); 43 | if (conversationStorage == null || conversationStorage 44 | .getConversationType() != ConversationModel.ConversationType.CLICK) 45 | return; 46 | event.getNpc().tryStartConversation(event.getPlayer()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/CustomizationLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import com.google.common.collect.Iterables; 4 | import io.github.gonalez.znpcs.cache.CachePackage; 5 | import io.github.gonalez.znpcs.cache.TypeCache; 6 | import org.bukkit.entity.Entity; 7 | import org.bukkit.entity.EntityType; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class CustomizationLoader { 14 | private final Class entityClass; 15 | 16 | private final Map methods; 17 | 18 | public CustomizationLoader(EntityType entityType, Iterable methodsName) { 19 | this(entityType.getEntityClass(), methodsName); 20 | } 21 | 22 | protected CustomizationLoader(Class entityClass, Iterable methodsName) { 23 | this.entityClass = entityClass; 24 | this.methods = loadMethods(methodsName); 25 | } 26 | 27 | protected Map loadMethods(Iterable iterable) { 28 | Map builder = new HashMap<>(); 29 | for (Method method : this.entityClass.getMethods()) { 30 | if (!builder.containsKey(method.getName()) && 31 | Iterables.contains(iterable, method.getName())) { 32 | for (Class parameter : method.getParameterTypes()) { 33 | TypeProperty typeProperty = TypeProperty.forType(parameter); 34 | if (typeProperty == null && parameter.isEnum()) 35 | (new TypeCache.BaseCache.EnumLoader((new TypeCache.CacheBuilder(CachePackage.DEFAULT)) 36 | .withClassName(parameter.getTypeName()))).load(); 37 | } 38 | builder.put(method.getName(), method); 39 | } 40 | } 41 | return builder; 42 | } 43 | 44 | public boolean contains(String name) { 45 | return this.methods.containsKey(name); 46 | } 47 | 48 | public Map getMethods() { 49 | return this.methods; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/FunctionContext.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | public interface FunctionContext { 4 | NPC getNPC(); 5 | 6 | interface WithValue extends FunctionContext { 7 | String getValue(); 8 | } 9 | 10 | class DefaultContext implements FunctionContext { 11 | private final NPC npc; 12 | 13 | public DefaultContext(NPC npc) { 14 | this.npc = npc; 15 | } 16 | 17 | public NPC getNPC() { 18 | return this.npc; 19 | } 20 | } 21 | 22 | class ContextWithValue extends DefaultContext implements WithValue { 23 | private final String value; 24 | 25 | public ContextWithValue(NPC npc, String value) { 26 | super(npc); 27 | this.value = value; 28 | } 29 | 30 | public String getValue() { 31 | return this.value; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/FunctionFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import io.github.gonalez.znpcs.npc.function.GlowFunction; 6 | import io.github.gonalez.znpcs.utility.GuavaCollectors; 7 | 8 | public final class FunctionFactory { 9 | public static ImmutableList WITHOUT_FUNCTION = ImmutableList.of(new NPCFunction.WithoutFunction("look"), new NPCFunction.WithoutFunctionSelfUpdate("holo"), new NPCFunction.WithoutFunctionSelfUpdate("mirror")); 10 | 11 | public static ImmutableList WITH_FUNCTION = ImmutableList.of(new GlowFunction()); 12 | 13 | public static ImmutableList ALL = 14 | ImmutableList.builder() 15 | .addAll(WITHOUT_FUNCTION) 16 | .addAll(WITH_FUNCTION) 17 | .build(); 18 | 19 | public static ImmutableMap BY_NAME; 20 | 21 | static { 22 | BY_NAME = ALL.stream().collect(GuavaCollectors.toImmutableMap(NPCFunction::getName, function -> function)); 23 | } 24 | 25 | public static NPCFunction findFunctionForName(String name) { 26 | return BY_NAME.get(name); 27 | } 28 | 29 | public static ImmutableList findFunctionsForNpc(NPC npc) { 30 | return ALL.stream() 31 | .filter(function -> isTrue(npc, function)) 32 | .collect(GuavaCollectors.toImmutableList()); 33 | } 34 | 35 | public static boolean isTrue(NPC npc, NPCFunction function) { 36 | return npc.getNpcPojo().getFunctions().getOrDefault(function.getName(), Boolean.FALSE); 37 | } 38 | 39 | public static boolean isTrue(NPC npc, String function) { 40 | return isTrue(npc, findFunctionForName(function)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/ItemSlot.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | public enum ItemSlot { 4 | HELMET(5), 5 | CHESTPLATE(4), 6 | LEGGINGS(3), 7 | BOOTS(2), 8 | OFFHAND(1), 9 | HAND(0); 10 | 11 | private final int slot; 12 | 13 | private final int slotOld; 14 | 15 | ItemSlot(int slot) { 16 | this.slot = slot; 17 | this.slotOld = (slot == 0) ? 0 : (slot - 1); 18 | } 19 | 20 | public int getSlot() { 21 | return this.slot; 22 | } 23 | 24 | public int getSlotOld() { 25 | return this.slotOld; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NPCAction.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import io.github.gonalez.znpcs.ServersNPC; 5 | import io.github.gonalez.znpcs.npc.event.ClickType; 6 | import io.github.gonalez.znpcs.user.ZUser; 7 | import io.github.gonalez.znpcs.utility.Utils; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.ChatColor; 10 | 11 | public class NPCAction { 12 | private final ActionType actionType; 13 | 14 | private final ClickType clickType; 15 | 16 | private final String action; 17 | 18 | private int delay; 19 | 20 | public NPCAction(ActionType actionType, ClickType clickType, String action, int delay) { 21 | this.actionType = actionType; 22 | this.clickType = clickType; 23 | this.action = action; 24 | this.delay = delay; 25 | } 26 | 27 | public NPCAction(String actionType, String action) { 28 | this(ActionType.valueOf(actionType), ClickType.DEFAULT, action, 0); 29 | } 30 | 31 | public ActionType getActionType() { 32 | return this.actionType; 33 | } 34 | 35 | public ClickType getClickType() { 36 | return this.clickType; 37 | } 38 | 39 | public String getAction() { 40 | return this.action; 41 | } 42 | 43 | public int getDelay() { 44 | return this.delay; 45 | } 46 | 47 | public void setDelay(int delay) { 48 | this.delay = delay; 49 | } 50 | 51 | public long getFixedDelay() { 52 | return 1000000000L * this.delay; 53 | } 54 | 55 | public void run(ZUser user, String action) { 56 | this.actionType.run(user, Utils.PLACEHOLDER_SUPPORT ? Utils.getWithPlaceholders(action, user.toPlayer()) : action); 57 | } 58 | 59 | public String toString() { 60 | return MoreObjects.toStringHelper(this) 61 | .add("actionType", this.actionType) 62 | .add("clickType", this.clickType) 63 | .add("action", this.action) 64 | .add("delay", this.delay) 65 | .toString(); 66 | } 67 | 68 | enum ActionType { 69 | CMD { 70 | public void run(ZUser user, String actionValue) { 71 | user.toPlayer().performCommand(actionValue); 72 | } 73 | }, 74 | CONSOLE { 75 | public void run(ZUser user, String actionValue) { 76 | Bukkit.dispatchCommand(Bukkit.getConsoleSender(), actionValue); 77 | } 78 | }, 79 | CHAT { 80 | public void run(ZUser user, String actionValue) { 81 | user.toPlayer().chat(actionValue); 82 | } 83 | }, 84 | MESSAGE { 85 | public void run(ZUser user, String actionValue) { 86 | user.toPlayer().sendMessage(ChatColor.translateAlternateColorCodes('&', actionValue)); 87 | } 88 | }, 89 | SERVER { 90 | public void run(ZUser user, String actionValue) { 91 | ServersNPC.BUNGEE_UTILS.sendPlayerToServer(user.toPlayer(), actionValue); 92 | } 93 | }; 94 | 95 | public abstract void run(ZUser param1ZUser, String param1String); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NPCFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | public abstract class NPCFunction { 4 | private final String name; 5 | 6 | public enum ResultType { 7 | SUCCESS, FAIL 8 | } 9 | 10 | public NPCFunction(String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return this.name; 16 | } 17 | 18 | protected abstract boolean allow(NPC paramNPC); 19 | 20 | protected abstract ResultType runFunction(NPC paramNPC, FunctionContext paramFunctionContext); 21 | 22 | public void doRunFunction(NPC npc, FunctionContext functionContext) { 23 | if (!allow(npc)) 24 | return; 25 | ResultType resultType = runFunction(npc, functionContext); 26 | if (resultType == ResultType.SUCCESS) 27 | npc.getNpcPojo().getFunctions().put(getName(), !isTrue(npc)); 28 | } 29 | 30 | protected ResultType resolve(NPC npc) { 31 | throw new IllegalStateException("resolve is not implemented."); 32 | } 33 | 34 | public boolean isTrue(NPC npc) { 35 | return FunctionFactory.isTrue(npc, this); 36 | } 37 | 38 | public static class WithoutFunction extends NPCFunction { 39 | public WithoutFunction(String name) { 40 | super(name); 41 | } 42 | 43 | protected ResultType runFunction(NPC npc, FunctionContext functionContext) { 44 | return ResultType.SUCCESS; 45 | } 46 | 47 | protected boolean allow(NPC npc) { 48 | return true; 49 | } 50 | 51 | protected ResultType resolve(NPC npc) { 52 | return ResultType.SUCCESS; 53 | } 54 | } 55 | 56 | public static class WithoutFunctionSelfUpdate extends WithoutFunction { 57 | public WithoutFunctionSelfUpdate(String name) { 58 | super(name); 59 | } 60 | 61 | protected ResultType runFunction(NPC npc, FunctionContext functionContext) { 62 | npc.deleteViewers(); 63 | return ResultType.SUCCESS; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NPCModel.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import io.github.gonalez.znpcs.npc.conversation.ConversationModel; 4 | import io.github.gonalez.znpcs.utility.location.ZLocation; 5 | import org.bukkit.inventory.ItemStack; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class NPCModel { 14 | private static final String EMPTY_STRING = ""; 15 | 16 | private int id; 17 | 18 | private double hologramHeight; 19 | 20 | private String skin; 21 | 22 | private String signature; 23 | 24 | private String skinName = "Steve"; 25 | 26 | private String pathName; 27 | 28 | private String glowName; 29 | 30 | private ConversationModel conversation; 31 | 32 | private ZLocation location; 33 | 34 | private NPCType npcType; 35 | 36 | private List hologramLines; 37 | 38 | private int refreshSkinDuration; 39 | 40 | private List clickActions; 41 | 42 | private Map npcEquip; 43 | 44 | private Map npcFunctions; 45 | 46 | private Map customizationMap; 47 | 48 | public NPCModel(int id) { 49 | this(); 50 | this.id = id; 51 | this.skin = ""; 52 | this.signature = ""; 53 | this.npcType = NPCType.PLAYER; 54 | } 55 | 56 | private NPCModel() { 57 | this.hologramLines = Collections.singletonList("/znpcs lines"); 58 | this.clickActions = new ArrayList<>(); 59 | this.npcEquip = new HashMap<>(); 60 | this.customizationMap = new HashMap<>(); 61 | this.npcFunctions = new HashMap<>(); 62 | this.npcFunctions.put("holo", Boolean.TRUE); 63 | } 64 | 65 | public int getId() { 66 | return this.id; 67 | } 68 | 69 | public void setId(int id) { 70 | this.id = id; 71 | } 72 | 73 | public NPCModel withId(int id) { 74 | setId(id); 75 | return this; 76 | } 77 | 78 | public double getHologramHeight() { 79 | return this.hologramHeight; 80 | } 81 | 82 | public void setHologramHeight(double hologramHeight) { 83 | this.hologramHeight = hologramHeight; 84 | } 85 | 86 | public NPCModel withHologramHeight(double hologramHeight) { 87 | setHologramHeight(hologramHeight); 88 | return this; 89 | } 90 | 91 | public String getSkin() { 92 | return this.skin; 93 | } 94 | 95 | public void setSkin(String skin) { 96 | this.skin = skin; 97 | } 98 | 99 | public NPCModel withSkin(String skin) { 100 | setSkin(skin); 101 | return this; 102 | } 103 | 104 | public String getSignature() { 105 | return this.signature; 106 | } 107 | 108 | public void setSignature(String signature) { 109 | this.signature = signature; 110 | } 111 | 112 | public NPCModel withSignature(String signature) { 113 | setSignature(signature); 114 | return this; 115 | } 116 | 117 | public String getPathName() { 118 | return this.pathName; 119 | } 120 | 121 | public void setPathName(String pathName) { 122 | this.pathName = pathName; 123 | } 124 | 125 | public NPCModel withPathName(String pathName) { 126 | setPathName(pathName); 127 | return this; 128 | } 129 | 130 | public String getGlowName() { 131 | return this.glowName; 132 | } 133 | 134 | public void setGlowName(String glowName) { 135 | this.glowName = glowName; 136 | } 137 | 138 | public NPCModel withGlowName(String glowName) { 139 | setGlowName(this.pathName); 140 | return this; 141 | } 142 | 143 | public ConversationModel getConversation() { 144 | return this.conversation; 145 | } 146 | 147 | public void setConversation(ConversationModel conversation) { 148 | this.conversation = conversation; 149 | } 150 | 151 | public NPCModel withConversation(ConversationModel conversation) { 152 | setConversation(conversation); 153 | return this; 154 | } 155 | 156 | public List getHologramLines() { 157 | return this.hologramLines; 158 | } 159 | 160 | public void setHologramLines(List hologramLines) { 161 | this.hologramLines = hologramLines; 162 | } 163 | 164 | public NPCModel withHologramLines(List hologramLines) { 165 | setHologramLines(hologramLines); 166 | return this; 167 | } 168 | 169 | public ZLocation getLocation() { 170 | return this.location; 171 | } 172 | 173 | public void setLocation(ZLocation location) { 174 | this.location = location; 175 | } 176 | 177 | public NPCModel withLocation(ZLocation location) { 178 | setLocation(location); 179 | return this; 180 | } 181 | 182 | public NPCType getNpcType() { 183 | return this.npcType; 184 | } 185 | 186 | public void setNpcType(NPCType npcType) { 187 | this.npcType = npcType; 188 | } 189 | 190 | public NPCModel withNpcType(NPCType npcType) { 191 | setNpcType(npcType); 192 | return this; 193 | } 194 | 195 | public int getRefreshSkinDuration() { 196 | return refreshSkinDuration; 197 | } 198 | 199 | public void setRefreshSkinDuration(int refreshSkinDuration) { 200 | this.refreshSkinDuration = refreshSkinDuration; 201 | } 202 | 203 | public NPCModel withRefreshSkinDuration(int refreshSkinDuration) { 204 | setRefreshSkinDuration(refreshSkinDuration); 205 | return this; 206 | } 207 | 208 | public List getClickActions() { 209 | return this.clickActions; 210 | } 211 | 212 | public void setClickActions(List clickActions) { 213 | this.clickActions = clickActions; 214 | } 215 | 216 | public NPCModel withClickActions(List clickActions) { 217 | setClickActions(clickActions); 218 | return this; 219 | } 220 | 221 | public Map getNpcEquip() { 222 | return this.npcEquip; 223 | } 224 | 225 | public void setNpcEquip(Map npcEquip) { 226 | this.npcEquip = npcEquip; 227 | } 228 | 229 | public NPCModel withNpcEquip(Map npcEquip) { 230 | setNpcEquip(npcEquip); 231 | return this; 232 | } 233 | 234 | public Map getCustomizationMap() { 235 | return this.customizationMap; 236 | } 237 | 238 | public void setCustomizationMap(Map customizationMap) { 239 | this.customizationMap = customizationMap; 240 | } 241 | 242 | public NPCModel withCustomizationMap(Map customizationMap) { 243 | setCustomizationMap(customizationMap); 244 | return this; 245 | } 246 | 247 | public Map getFunctions() { 248 | return this.npcFunctions; 249 | } 250 | 251 | public void setFunctions(Map npcFunctions) { 252 | this.npcFunctions = npcFunctions; 253 | } 254 | 255 | public NPCModel withFunctionValues(Map npcFunctions) { 256 | setFunctions(npcFunctions); 257 | return this; 258 | } 259 | 260 | public void setSkinName(String skinName) { 261 | this.skinName = skinName; 262 | } 263 | 264 | public String getSkinName() { 265 | return skinName; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NPCSkin.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | 5 | public class NPCSkin { 6 | private static final String[] EMPTY_ARRAY = new String[] { "", "" }; 7 | 8 | private static final int LAYER_INDEX = SkinLayerValues.findLayerByVersion(); 9 | 10 | private final String texture; 11 | 12 | private final String signature; 13 | 14 | protected NPCSkin(String... values) { 15 | if (values.length < 1) 16 | throw new IllegalArgumentException("Length cannot be zero or negative."); 17 | this.texture = values[0]; 18 | this.signature = values[1]; 19 | } 20 | 21 | public String getTexture() { 22 | return this.texture; 23 | } 24 | 25 | public String getSignature() { 26 | return this.signature; 27 | } 28 | 29 | public int getLayerIndex() { 30 | return LAYER_INDEX; 31 | } 32 | 33 | public static NPCSkin forValues(String... values) { 34 | return new NPCSkin((values.length > 0) ? values : EMPTY_ARRAY); 35 | } 36 | 37 | enum SkinLayerValues { 38 | V8(8, 12), 39 | V9(10, 13), 40 | V14(14, 15), 41 | V16(15, 16), 42 | V17(17, 17), 43 | V18(18, 17); 44 | 45 | final int minVersion; 46 | 47 | final int layerValue; 48 | 49 | SkinLayerValues(int minVersion, int layerValue) { 50 | this.minVersion = minVersion; 51 | this.layerValue = layerValue; 52 | } 53 | 54 | static int findLayerByVersion() { 55 | int value = V8.layerValue; 56 | for (SkinLayerValues skinLayerValue : values()) { 57 | if (Utils.BUKKIT_VERSION >= skinLayerValue.minVersion) 58 | value = skinLayerValue.layerValue; 59 | } 60 | return value; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NPCType.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import io.github.gonalez.znpcs.UnexpectedCallException; 4 | import io.github.gonalez.znpcs.cache.CacheRegistry; 5 | import io.github.gonalez.znpcs.cache.TypeCache; 6 | import io.github.gonalez.znpcs.utility.Utils; 7 | import org.bukkit.entity.EntityType; 8 | 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | import java.util.Arrays; 13 | import java.util.Optional; 14 | 15 | /** 16 | * List of supported entity types for a {@link NPC}. 17 | */ 18 | public enum NPCType { 19 | PLAYER(CacheRegistry.ENTITY_PLAYER_CLASS, 0), 20 | ARMOR_STAND(CacheRegistry.ENTITY_ARMOR_STAND_CLASS, 0, "setSmall", "setArms"), 21 | CREEPER(CacheRegistry.ENTITY_CREEPER_CLASS, -0.15, "setPowered"), 22 | BAT(CacheRegistry.ENTITY_BAT_CLASS, -0.5, "setAwake"), 23 | BLAZE(CacheRegistry.ENTITY_BLAZE_CLASS, 0), 24 | CAVE_SPIDER(CacheRegistry.ENTITY_CAVE_SPIDER_CLASS, -1), 25 | COW(CacheRegistry.ENTITY_COW_CLASS, -0.25, "setAge"), 26 | CHICKEN(CacheRegistry.ENTITY_CHICKEN_CLASS, -1, "setAge"), 27 | ENDER_DRAGON(CacheRegistry.ENTITY_ENDER_DRAGON_CLASS, 1.5), 28 | ENDERMAN(CacheRegistry.ENTITY_ENDERMAN_CLASS, 0.7), 29 | ENDERMITE(CacheRegistry.ENTITY_ENDERMITE_CLASS, -1.5), 30 | GHAST(CacheRegistry.ENTITY_GHAST_CLASS, 3), 31 | IRON_GOLEM(CacheRegistry.ENTITY_IRON_GOLEM_CLASS, 0.75), 32 | GIANT(CacheRegistry.ENTITY_GIANT_ZOMBIE_CLASS, 11), 33 | GUARDIAN(CacheRegistry.ENTITY_GUARDIAN_CLASS, -0.7), 34 | HORSE(CacheRegistry.ENTITY_HORSE_CLASS, 0, "setStyle", "setAge", "setColor", "setVariant"), 35 | LLAMA(CacheRegistry.ENTITY_LLAMA_CLASS, 0, "setAge"), 36 | MAGMA_CUBE(CacheRegistry.ENTITY_MAGMA_CUBE_CLASS, -1.25, "setSize"), 37 | MUSHROOM_COW(CacheRegistry.ENTITY_MUSHROOM_COW_CLASS, -0.25, "setAge"), 38 | OCELOT(CacheRegistry.ENTITY_OCELOT_CLASS, -1, "setCatType", "setAge"), 39 | PARROT(CacheRegistry.ENTITY_PARROT_CLASS, -1.5, "setVariant"), 40 | PIG(CacheRegistry.ENTITY_PIG_CLASS, -1, "setAge"), 41 | PANDA(CacheRegistry.ENTITY_PANDA_CLASS, -0.6, "setAge", "setMainGene", "setHiddenGene"), 42 | RABBIT(CacheRegistry.ENTITY_RABBIT_CLASS, -1, "setRabbitType"), 43 | POLAR_BEAR(CacheRegistry.ENTITY_POLAR_BEAR_CLASS, -0.5), 44 | SHEEP(CacheRegistry.ENTITY_SHEEP_CLASS, -0.5, "setAge", "setSheared", "setColor"), 45 | SILVERFISH(CacheRegistry.ENTITY_SILVERFISH_CLASS, -1.5), 46 | SNOWMAN(CacheRegistry.ENTITY_SNOWMAN_CLASS, 0, "setHasPumpkin", "setDerp"), 47 | SKELETON(CacheRegistry.ENTITY_SKELETON_CLASS, 0), 48 | SHULKER(CacheRegistry.ENTITY_SHULKER_CLASS, 0), 49 | SLIME(CacheRegistry.ENTITY_SLIME_CLASS, -1.25, "setSize"), 50 | SPIDER(CacheRegistry.ENTITY_SPIDER_CLASS, -1), 51 | SQUID(CacheRegistry.ENTITY_SQUID_CLASS, -1), 52 | VILLAGER(CacheRegistry.ENTITY_VILLAGER_CLASS, 0, "setProfession", "setVillagerType", "setAge"), 53 | WITCH(CacheRegistry.ENTITY_WITCH_CLASS, 0.5), 54 | WITHER(CacheRegistry.ENTITY_WITHER_CLASS, 1.75), 55 | ZOMBIE(CacheRegistry.ENTITY_ZOMBIE_CLASS, 0, "setBaby"), 56 | WOLF(CacheRegistry.ENTITY_WOLF_CLASS, -1, "setSitting", "setTamed", "setAngry", "setAge", "setCollarColor"), 57 | FOX(CacheRegistry.ENTITY_FOX_CLASS, -1, "setFoxType", "setSitting", "setSleeping", "setAge", "setCrouching"), 58 | 59 | // v1.17+ 60 | BEE(CacheRegistry.ENTITY_BEE_CLASS, -1, "setAnger", "setHasNectar", "setHasStung"), 61 | 62 | TURTLE(CacheRegistry.ENTITY_TURTLE, -1), 63 | WARDEN(CacheRegistry.ENTITY_WARDEN, 1), 64 | 65 | AXOLOTL(CacheRegistry.ENTITY_AXOLOTL_CLASS, -1, "setVariant", "setAge"), 66 | GOAT(CacheRegistry.ENTITY_GOAT_CLASS, -0.5, "setScreamingGoat", "setAge"); 67 | 68 | /** A empty string. */ 69 | private static final String EMPTY_STRING = ""; 70 | /** The hologram height for the entity type. */ 71 | private final double holoHeight; 72 | /** The entity customization loader. */ 73 | private final CustomizationLoader customizationLoader; 74 | /** The entity spawn packet. */ 75 | private final Constructor constructor; 76 | /** The entity bukkit type. */ 77 | private EntityType bukkitEntityType; 78 | /** The entity nms type. */ 79 | private Object nmsEntityType; 80 | 81 | /** 82 | * Creates a new {@link NPCType}. 83 | * 84 | * @param entityClass The entity class. 85 | * @param newName The entity name for newer versions. 86 | * @param holoHeight The hologram height for the entity. 87 | * @param methods The possible customization methods for the entity. 88 | */ 89 | NPCType(Class entityClass, 90 | String newName, 91 | double holoHeight, 92 | String... methods) { 93 | this.holoHeight = holoHeight; 94 | this.customizationLoader = entityClass == null ? 95 | null : new CustomizationLoader(this.bukkitEntityType = 96 | EntityType.valueOf(newName.length() > 0 ? newName : name()), Arrays.asList(methods)); 97 | // onLoad #constructor 98 | if (entityClass == null 99 | || entityClass.isAssignableFrom(CacheRegistry.ENTITY_PLAYER_CLASS)) { // check if entity constructor can be load 100 | constructor = null; 101 | return; 102 | } 103 | 104 | try { 105 | if (Utils.versionNewer(14)) { 106 | nmsEntityType = ((Optional) CacheRegistry.ENTITY_TYPES_A_METHOD.load().invoke(null, bukkitEntityType.getKey().getKey().toLowerCase())).get(); 107 | constructor = entityClass.getConstructor(CacheRegistry.ENTITY_TYPES_CLASS, CacheRegistry.WORLD_CLASS); 108 | } else { 109 | constructor = entityClass.getConstructor(CacheRegistry.WORLD_CLASS); 110 | } 111 | } catch (ReflectiveOperationException operationException) { 112 | // can't get entity constructor 113 | throw new UnexpectedCallException(operationException); 114 | } 115 | } 116 | 117 | /** 118 | * Creates a {@link NPCType}. 119 | * 120 | * @param entityClass The entity class. 121 | * @param holoHeight The hologram height for the entity. 122 | * @param customization The possible customization methods for the entity. 123 | */ 124 | NPCType(Class entityClass, 125 | double holoHeight, 126 | String... customization) { 127 | this(entityClass, EMPTY_STRING, holoHeight, customization); 128 | } 129 | 130 | /** 131 | * Returns the hologram height for the entity. 132 | */ 133 | public double getHoloHeight() { 134 | return holoHeight; 135 | } 136 | 137 | /** 138 | * Returns the entity spawn packet. 139 | */ 140 | public Constructor getConstructor() { 141 | return constructor; 142 | } 143 | 144 | /** 145 | * Returns the entity nms type. 146 | */ 147 | public Object getNmsEntityType() { 148 | return nmsEntityType; 149 | } 150 | 151 | /** 152 | * Returns the entity customization loader. 153 | */ 154 | public CustomizationLoader getCustomizationLoader() { 155 | return customizationLoader; 156 | } 157 | 158 | /** 159 | * Converts the provided types to the corresponding {@code method} types. 160 | * 161 | * @param strings The array of strings to convert. 162 | * @param method The method. 163 | * @return The converted array of primitive types. 164 | */ 165 | public static Object[] arrayToPrimitive(String[] strings, Method method) { 166 | Class[] methodParameterTypes = method.getParameterTypes(); 167 | Object[] newArray = new Object[methodParameterTypes.length]; 168 | for (int i = 0; i < methodParameterTypes.length; i++) { 169 | TypeProperty typeProperty = TypeProperty.forType(methodParameterTypes[i]); 170 | if (typeProperty != null) { 171 | newArray[i] = typeProperty.getFunction().apply(strings[i]); 172 | } else { 173 | // use cache values 174 | newArray[i] = TypeCache.ClassCache.find(strings[i], methodParameterTypes[i]); 175 | } 176 | } 177 | return newArray; 178 | } 179 | 180 | /** 181 | * Changes the npc customization. 182 | * 183 | * @param npc The npc to update the customization for. 184 | * @param name The method name. 185 | * @param values The method values. 186 | * @throws IllegalStateException If can't invoke method. 187 | */ 188 | public void updateCustomization(NPC npc, 189 | String name, 190 | String[] values) { 191 | if (!customizationLoader.contains(name)) { 192 | // method not found for npc type 193 | return; 194 | } 195 | try { 196 | Method method = customizationLoader.getMethods().get(name); 197 | method.invoke(npc.getBukkitEntity(), arrayToPrimitive(values, method)); 198 | // update new customization for the npc 199 | npc.updateMetadata(npc.getViewers()); 200 | } catch (IllegalAccessException | InvocationTargetException e) { 201 | throw new IllegalStateException("can't invoke method: " + name, e); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/NamingType.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | 5 | public enum NamingType { 6 | DEFAULT { 7 | public String resolve(NPC npc) { 8 | return Utils.randomString(6); 9 | } 10 | }; 11 | 12 | private static final int FIXED_LENGTH = 6; 13 | 14 | public abstract String resolve(NPC paramNPC); 15 | } 16 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/TypeProperty.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc; 2 | 3 | import java.util.function.Function; 4 | 5 | public enum TypeProperty { 6 | STRING(String::toString), 7 | BOOLEAN(Boolean::parseBoolean), 8 | INT(Integer::parseInt), 9 | DOUBLE(Double::parseDouble), 10 | FLOAT(Float::parseFloat), 11 | SHORT(Short::parseShort), 12 | LONG(Long::parseLong); 13 | 14 | private final Function function; 15 | 16 | TypeProperty(Function function) { 17 | this.function = function; 18 | } 19 | 20 | public Function getFunction() { 21 | return this.function; 22 | } 23 | 24 | public static TypeProperty forType(Class primitiveType) { 25 | if (primitiveType == String.class) 26 | return STRING; 27 | if (primitiveType == boolean.class) 28 | return BOOLEAN; 29 | if (primitiveType == int.class) 30 | return INT; 31 | if (primitiveType == double.class) 32 | return DOUBLE; 33 | if (primitiveType == float.class) 34 | return FLOAT; 35 | if (primitiveType == short.class) 36 | return SHORT; 37 | if (primitiveType == long.class) 38 | return LONG; 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/conversation/Conversation.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.conversation; 2 | 3 | import io.github.gonalez.znpcs.ZNPConfigUtils; 4 | import io.github.gonalez.znpcs.configuration.ConversationsConfiguration; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Conversation { 9 | private final String name; 10 | 11 | private final List texts; 12 | 13 | private int radius = 5; 14 | 15 | private int delay = 10; 16 | 17 | public Conversation(String name) { 18 | this(name, new ArrayList<>()); 19 | } 20 | 21 | protected Conversation(String name, List text) { 22 | this.name = name; 23 | this.texts = text; 24 | } 25 | 26 | public String getName() { 27 | return this.name; 28 | } 29 | 30 | public List getTexts() { 31 | return this.texts; 32 | } 33 | 34 | public int getDelay() { 35 | return this.delay; 36 | } 37 | 38 | public int getRadius() { 39 | return this.radius; 40 | } 41 | 42 | public void setDelay(int delay) { 43 | this.delay = delay; 44 | } 45 | 46 | public void setRadius(int radius) { 47 | this.radius = radius; 48 | } 49 | 50 | public static Conversation forName(String name) { 51 | return ZNPConfigUtils.getConfig(ConversationsConfiguration.class).conversationList.stream() 52 | .filter(conversation -> conversation.getName().equalsIgnoreCase(name)) 53 | .findFirst() 54 | .orElse(null); 55 | } 56 | 57 | public static boolean exists(String name) { 58 | return ZNPConfigUtils.getConfig(ConversationsConfiguration.class).conversationList.stream() 59 | .anyMatch(conversation -> conversation.getName().equalsIgnoreCase(name)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/conversation/ConversationKey.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.conversation; 2 | 3 | import com.google.common.base.Splitter; 4 | import io.github.gonalez.znpcs.npc.NPCAction; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.StreamSupport; 10 | 11 | public class ConversationKey { 12 | private static final Splitter SPACE_SPLITTER = Splitter.on(" "); 13 | 14 | private final List lines; 15 | 16 | private final List actions; 17 | 18 | private int delay = 1; 19 | 20 | private String soundName; 21 | 22 | public ConversationKey(String line) { 23 | this(SPACE_SPLITTER.split(line)); 24 | } 25 | 26 | public ConversationKey(Iterable line) { 27 | this 28 | .lines = StreamSupport.stream(line.spliterator(), false).map(String::toString).collect(Collectors.toList()); 29 | this.actions = new ArrayList<>(); 30 | } 31 | 32 | public List getLines() { 33 | return this.lines; 34 | } 35 | 36 | public int getDelay() { 37 | return this.delay; 38 | } 39 | 40 | public String getSoundName() { 41 | return this.soundName; 42 | } 43 | 44 | public List getActions() { 45 | return this.actions; 46 | } 47 | 48 | public void setDelay(int delay) { 49 | this.delay = delay; 50 | } 51 | 52 | public void setSoundName(String soundName) { 53 | this.soundName = soundName; 54 | } 55 | 56 | public String getTextFormatted() { 57 | if (this.lines.isEmpty()) 58 | return ""; 59 | String text = this.lines.iterator().next(); 60 | int fixedLength = Math.min(text.length(), 28); 61 | return text.substring(0, fixedLength); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/conversation/ConversationModel.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.conversation; 2 | 3 | import io.github.gonalez.znpcs.npc.NPC; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | import java.util.stream.Stream; 10 | 11 | public class ConversationModel { 12 | private String conversationName; 13 | 14 | private ConversationType conversationType; 15 | 16 | private final transient Map lastStarted = new HashMap<>(); 17 | 18 | public ConversationModel(String conversationName, String conversationType) { 19 | this.conversationName = conversationName; 20 | try { 21 | this.conversationType = ConversationType.valueOf(conversationType.toUpperCase()); 22 | } catch (IllegalArgumentException exception) { 23 | throw new IllegalStateException("can't find conversation type " + conversationType); 24 | } 25 | } 26 | 27 | public String getConversationName() { 28 | return this.conversationName; 29 | } 30 | 31 | public ConversationType getConversationType() { 32 | return this.conversationType; 33 | } 34 | 35 | public Conversation getConversation() { 36 | return Conversation.forName(this.conversationName); 37 | } 38 | 39 | public void startConversation(NPC npc, Player player) { 40 | if (!Conversation.exists(this.conversationName)) 41 | throw new IllegalStateException("can't find conversation " + this.conversationName); 42 | if (ConversationProcessor.isPlayerConversing(player.getUniqueId())) 43 | return; 44 | if (this.lastStarted.containsKey(player.getUniqueId())) { 45 | long lastConversationNanos = System.nanoTime() - this.lastStarted.get(player.getUniqueId()); 46 | if (lastConversationNanos < 1000000000L * getConversation().getDelay()) 47 | return; 48 | } 49 | this.lastStarted.remove(player.getUniqueId()); 50 | if (this.conversationType.canStart(npc, getConversation(), player)) { 51 | new ConversationProcessor(npc, this, player); 52 | this.lastStarted.put(player.getUniqueId(), System.nanoTime()); 53 | } 54 | } 55 | 56 | public boolean canRun(NPC npc, Player player) { 57 | return Stream.of(ConversationType.values()).anyMatch(conversationType1 -> !conversationType1.canStart(npc, getConversation(), player)); 58 | } 59 | 60 | private ConversationModel() {} 61 | 62 | public enum ConversationType { 63 | RADIUS { 64 | public boolean canStart(NPC npc, Conversation conversation, Player player) { 65 | return (player.getWorld() == npc.getLocation().getWorld() && player 66 | .getLocation().distance(npc.getLocation()) <= conversation.getRadius()); 67 | } 68 | }, 69 | CLICK { 70 | public boolean canStart(NPC npc, Conversation conversation, Player player) { 71 | return true; 72 | } 73 | }; 74 | 75 | abstract boolean canStart(NPC param1NPC, Conversation param1Conversation, Player param1Player); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/conversation/ConversationProcessor.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.conversation; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import io.github.gonalez.znpcs.ZNPConfigUtils; 5 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.npc.hologram.replacer.LineReplacer; 8 | import io.github.gonalez.znpcs.user.ZUser; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.Sound; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.scheduler.BukkitRunnable; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | 18 | public class ConversationProcessor { 19 | private static final Map RUNNING_CONVERSATIONS = new HashMap<>(); 20 | 21 | private static final String WHITE_SPACE = " "; 22 | 23 | private static final int CONVERSATION_DELAY = 20; 24 | 25 | private final NPC npc; 26 | 27 | private final ConversationModel conversationModel; 28 | 29 | private final Player player; 30 | 31 | private int conversationIndex = 0; 32 | 33 | private long conversationIndexDelay = System.nanoTime(); 34 | 35 | public ConversationProcessor(NPC npc, ConversationModel conversationModel, Player player) { 36 | if (conversationModel.getConversation().getTexts().isEmpty()) 37 | throw new IllegalStateException("conversation should have a text."); 38 | this.npc = npc; 39 | this.conversationModel = conversationModel; 40 | this.player = player; 41 | RUNNING_CONVERSATIONS.put(player.getUniqueId(), conversationModel.getConversationName()); 42 | start(); 43 | } 44 | 45 | private void start() { 46 | ServersNPC.SCHEDULER.runTaskTimer(new BukkitRunnable() { 47 | public void run() { 48 | if (Bukkit.getPlayer(ConversationProcessor.this.player.getUniqueId()) == null || ConversationProcessor.this 49 | .conversationIndex > ConversationProcessor.this.conversationModel.getConversation().getTexts().size() - 1 || ConversationProcessor.this 50 | .conversationModel.canRun(ConversationProcessor.this.npc, ConversationProcessor.this.player)) { 51 | ConversationProcessor.RUNNING_CONVERSATIONS.remove(ConversationProcessor.this.player.getUniqueId()); 52 | cancel(); 53 | return; 54 | } 55 | ConversationKey conversationKey = ConversationProcessor.this.conversationModel.getConversation().getTexts().get(ConversationProcessor.this.conversationIndex); 56 | long conversationDelayNanos = System.nanoTime() - ConversationProcessor.this.conversationIndexDelay; 57 | if (ConversationProcessor.this.conversationIndex != 0 && conversationDelayNanos < 1000000000L * conversationKey 58 | .getDelay()) 59 | return; 60 | ZUser user = ZUser.find(ConversationProcessor.this.player); 61 | conversationKey.getLines().forEach(s -> ConversationProcessor.this.player.sendMessage(LineReplacer.makeAll(user, s) 62 | .replace(ZNPConfigUtils.getConfig(ConfigConfiguration.class).replaceSymbol, " "))); 63 | if (conversationKey.getActions().size() > 0) 64 | conversationKey.getActions().forEach(action -> action.run(user, action.getAction())); 65 | if (conversationKey.getSoundName() != null && conversationKey 66 | .getSoundName().length() > 0) 67 | try { 68 | Sound sound = Sound.valueOf(conversationKey.getSoundName().toUpperCase()); 69 | ConversationProcessor.this.player.playSound(ConversationProcessor.this.player.getLocation(), sound, 0.2F, 1.0F); 70 | } catch (IllegalArgumentException illegalArgumentException) {} 71 | ConversationProcessor.this.conversationIndexDelay = System.nanoTime(); 72 | ConversationProcessor.this.conversationIndex++; 73 | } 74 | }, 5, 20); 75 | } 76 | 77 | public static boolean isPlayerConversing(UUID uuid) { 78 | return RUNNING_CONVERSATIONS.containsKey(uuid); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/event/ClickType.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.event; 2 | 3 | public enum ClickType { 4 | RIGHT, LEFT, DEFAULT; 5 | 6 | public static ClickType forName(String clickName) { 7 | if (clickName.startsWith("INTERACT")) 8 | return RIGHT; 9 | if (clickName.startsWith("ATTACK")) 10 | return LEFT; 11 | return DEFAULT; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/event/NPCInteractEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.event; 2 | 3 | import io.github.gonalez.znpcs.npc.NPC; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | public class NPCInteractEvent extends Event { 9 | private final Player player; 10 | 11 | private final ClickType clickType; 12 | 13 | private final NPC npc; 14 | 15 | private static final HandlerList handlerList = new HandlerList(); 16 | 17 | public NPCInteractEvent(Player player, ClickType clickType, NPC npc) { 18 | this.player = player; 19 | this.clickType = clickType; 20 | this.npc = npc; 21 | } 22 | 23 | public NPCInteractEvent(Player player, String clickType, NPC npc) { 24 | this(player, ClickType.forName(clickType), npc); 25 | } 26 | 27 | public Player getPlayer() { 28 | return this.player; 29 | } 30 | 31 | public NPC getNpc() { 32 | return this.npc; 33 | } 34 | 35 | public boolean isRightClick() { 36 | return (this.clickType == ClickType.RIGHT); 37 | } 38 | 39 | public boolean isLeftClick() { 40 | return (this.clickType == ClickType.LEFT); 41 | } 42 | 43 | public HandlerList getHandlers() { 44 | return handlerList; 45 | } 46 | 47 | public static HandlerList getHandlerList() { 48 | return handlerList; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/function/GlowFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.function; 2 | 3 | import io.github.gonalez.znpcs.cache.CacheRegistry; 4 | import io.github.gonalez.znpcs.npc.FunctionContext; 5 | import io.github.gonalez.znpcs.npc.FunctionFactory; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.npc.NPCFunction; 8 | 9 | public class GlowFunction extends NPCFunction { 10 | public GlowFunction() { 11 | super("glow"); 12 | } 13 | 14 | protected ResultType runFunction(NPC npc, FunctionContext functionContext) { 15 | if (!(functionContext instanceof FunctionContext.ContextWithValue)) { 16 | throw new IllegalStateException("invalid context type, " + functionContext.getClass().getSimpleName() + ", expected ContextWithValue."); 17 | } 18 | final String glowColorName = ((FunctionContext.ContextWithValue) functionContext).getValue(); 19 | try { 20 | // find the glow color enum 21 | final Object glowColor = CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, 22 | glowColorName == null || glowColorName.length() == 0 ? "WHITE" : glowColorName); 23 | if (glowColor == null) 24 | return ResultType.FAIL; 25 | npc.getNpcPojo().setGlowName(glowColorName); 26 | npc.setGlowColor(glowColor); 27 | CacheRegistry.SET_DATA_WATCHER_METHOD.load().invoke( 28 | CacheRegistry.GET_DATA_WATCHER_METHOD.load().invoke(npc.getNmsEntity()), 29 | CacheRegistry.DATA_WATCHER_OBJECT_CONSTRUCTOR.load().newInstance( 30 | 0, 31 | CacheRegistry.DATA_WATCHER_REGISTER_FIELD.load()), 32 | !FunctionFactory.isTrue(npc, this) ? (byte) 0x40 : (byte) 0x0); 33 | // update glow scoreboard packets 34 | npc.getPackets().getProxyInstance().update(npc.getPackets()); 35 | npc.deleteViewers(); 36 | return ResultType.SUCCESS; 37 | } catch (ReflectiveOperationException operationException) { 38 | return ResultType.FAIL; 39 | } 40 | } 41 | 42 | protected boolean allow(NPC npc) { 43 | return npc.getPackets().getProxyInstance().allowGlowColor(); 44 | } 45 | 46 | public NPCFunction.ResultType resolve(NPC npc) { 47 | return runFunction(npc, new FunctionContext.ContextWithValue(npc, npc.getNpcPojo().getGlowName())); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/hologram/Hologram.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.hologram; 2 | 3 | import io.github.gonalez.znpcs.UnexpectedCallException; 4 | import io.github.gonalez.znpcs.ZNPConfigUtils; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 7 | import io.github.gonalez.znpcs.npc.NPC; 8 | import io.github.gonalez.znpcs.npc.hologram.replacer.LineReplacer; 9 | import io.github.gonalez.znpcs.user.ZUser; 10 | import io.github.gonalez.znpcs.utility.Utils; 11 | import org.bukkit.Location; 12 | 13 | import javax.annotation.Nullable; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class Hologram { 19 | private static final String WHITESPACE = " "; 20 | 21 | private static final boolean NEW_METHOD = (Utils.BUKKIT_VERSION > 12); 22 | 23 | private final List hologramLines = new ArrayList<>(); 24 | 25 | private final NPC npc; 26 | 27 | public Hologram(NPC npc) { 28 | this.npc = npc; 29 | } 30 | 31 | /** 32 | * Called when creating a {@link Hologram}. 33 | */ 34 | public void createHologram() { 35 | npc.getViewers().forEach(this::delete); 36 | try { 37 | hologramLines.clear(); 38 | double y = 0; 39 | final Location location = npc.getLocation(); 40 | for (String line : npc.getNpcPojo().getHologramLines()) { 41 | boolean visible = !line.equalsIgnoreCase("%space%"); // determine if the line should be seen 42 | Object armorStand = CacheRegistry.ENTITY_CONSTRUCTOR.load().newInstance(CacheRegistry.GET_HANDLE_WORLD_METHOD.load().invoke(location.getWorld()), 43 | location.getX(), (location.getY() - 0.15) + (y), location.getZ()); 44 | if (visible) { 45 | CacheRegistry.SET_CUSTOM_NAME_VISIBLE_METHOD.load().invoke(armorStand, true); // entity name is not visible by default 46 | updateLine(line, armorStand, null); 47 | } 48 | CacheRegistry.SET_INVISIBLE_METHOD.load().invoke(armorStand, true); 49 | hologramLines.add(new HologramLine(line.replace(ZNPConfigUtils.getConfig(ConfigConfiguration.class).replaceSymbol, WHITESPACE), 50 | armorStand, (Integer) CacheRegistry.GET_ENTITY_ID.load().invoke(armorStand))); 51 | y+= ZNPConfigUtils.getConfig(ConfigConfiguration.class).lineSpacing; 52 | } 53 | setLocation(location, 0); 54 | npc.getPackets().flushCache("getHologramSpawnPacket"); 55 | npc.getViewers().forEach(this::spawn); 56 | } catch (ReflectiveOperationException operationException) { 57 | throw new UnexpectedCallException(operationException); 58 | } 59 | } 60 | 61 | /** 62 | * Spawns the hologram for the given player. 63 | * 64 | * @param user The player to spawn the hologram for. 65 | */ 66 | public void spawn(ZUser user) { 67 | hologramLines.forEach(hologramLine -> { 68 | try { 69 | Object entityPlayerPacketSpawn = npc.getPackets().getProxyInstance() 70 | .getHologramSpawnPacket(hologramLine.armorStand); 71 | Utils.sendPackets(user, entityPlayerPacketSpawn); 72 | } catch (ReflectiveOperationException operationException) { 73 | delete(user); 74 | } 75 | }); 76 | } 77 | 78 | /** 79 | * Deletes the hologram for the given player. 80 | * 81 | * @param user The player to remove the hologram for. 82 | */ 83 | public void delete(ZUser user) { 84 | hologramLines.forEach(hologramLine -> { 85 | try { 86 | Utils.sendPackets(user, npc.getPackets().getProxyInstance().getDestroyPacket(hologramLine.id)); 87 | } catch (ReflectiveOperationException operationException) { 88 | throw new UnexpectedCallException(operationException); 89 | } 90 | }); 91 | } 92 | 93 | /** 94 | * Updates the hologram text for the given player. 95 | * 96 | * @param user The player to update the hologram for. 97 | */ 98 | public void updateNames(ZUser user) { 99 | for (HologramLine hologramLine : hologramLines) { 100 | try { 101 | updateLine(hologramLine.line, hologramLine.armorStand, user); 102 | // update the line 103 | Object metaData = npc.getPackets().getProxyInstance().getMetadataPacket(hologramLine.id, hologramLine.armorStand); 104 | Utils.sendPackets( 105 | user, 106 | metaData); 107 | } catch (ReflectiveOperationException operationException) { 108 | throw new UnexpectedCallException(operationException); 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Updates the hologram location. 115 | */ 116 | public void updateLocation() { 117 | hologramLines.forEach(hologramLine -> { 118 | try { 119 | Object packet = CacheRegistry.PACKET_PLAY_OUT_ENTITY_TELEPORT_CONSTRUCTOR.load().newInstance(hologramLine.armorStand); 120 | npc.getViewers().forEach(player -> Utils.sendPackets(player, packet)); 121 | } catch (ReflectiveOperationException operationException) { 122 | throw new UnexpectedCallException(operationException); 123 | } 124 | }); 125 | } 126 | 127 | /** 128 | * Sets & updates the hologram location. 129 | * 130 | * @param location The new location. 131 | */ 132 | public void setLocation(Location location, double height) { 133 | location = location.clone().add(0, height, 0); 134 | try { 135 | double y = npc.getNpcPojo().getHologramHeight(); 136 | for (HologramLine hologramLine : hologramLines) { 137 | CacheRegistry.SET_LOCATION_METHOD.load().invoke(hologramLine.armorStand, 138 | location.getX(), (location.getY() - 0.15) + y, 139 | location.getZ(), location.getYaw(), location.getPitch()); 140 | y+=ZNPConfigUtils.getConfig(ConfigConfiguration.class).lineSpacing; 141 | } 142 | updateLocation(); 143 | } catch (ReflectiveOperationException operationException) { 144 | throw new UnexpectedCallException(operationException); 145 | } 146 | } 147 | 148 | /** 149 | * Updates a hologram line. 150 | * 151 | * @param line The new hologram line. 152 | * @param armorStand The hologram entity line. 153 | * @param user The player to update the line for. 154 | * @throws InvocationTargetException If cannot invoke method. 155 | * @throws IllegalAccessException If the method cannot be accessed. 156 | */ 157 | private void updateLine(String line, 158 | Object armorStand, 159 | @Nullable ZUser user) throws InvocationTargetException, IllegalAccessException { 160 | if (NEW_METHOD) { 161 | CacheRegistry.SET_CUSTOM_NAME_NEW_METHOD.load().invoke(armorStand, CacheRegistry.CRAFT_CHAT_MESSAGE_METHOD.load().invoke(null, LineReplacer.makeAll(user, line))); 162 | } else { 163 | CacheRegistry.SET_CUSTOM_NAME_OLD_METHOD.load().invoke(armorStand, LineReplacer.makeAll(user, line)); 164 | } 165 | } 166 | 167 | /** 168 | * Used to create new lines for a {@link Hologram}. 169 | */ 170 | private static class HologramLine { 171 | /** The hologram line string. */ 172 | private final String line; 173 | /** The hologram line entity. */ 174 | private final Object armorStand; 175 | /** The hologram line entity id. */ 176 | private final int id; 177 | 178 | /** 179 | * Creates a new line for the hologram. 180 | * 181 | * @param line The hologram line string. 182 | * @param armorStand The hologram entity. 183 | * @param id The hologram entity id. 184 | */ 185 | protected HologramLine(String line, 186 | Object armorStand, 187 | int id) { 188 | this.line = line; 189 | this.armorStand = armorStand; 190 | this.id = id; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/hologram/replacer/LineReplacer.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.hologram.replacer; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.UnmodifiableIterator; 5 | import io.github.gonalez.znpcs.user.ZUser; 6 | import io.github.gonalez.znpcs.utility.Utils; 7 | 8 | public interface LineReplacer { 9 | ImmutableList LINE_REPLACERS = ImmutableList.of(new RGBLine()); 10 | 11 | String make(String paramString); 12 | 13 | boolean isSupported(); 14 | 15 | static String makeAll(ZUser user, String string) { 16 | for (UnmodifiableIterator unmodifiableIterator = LINE_REPLACERS.iterator(); unmodifiableIterator.hasNext(); ) { 17 | LineReplacer lineReplacer = unmodifiableIterator.next(); 18 | if (!lineReplacer.isSupported()) 19 | continue; 20 | string = lineReplacer.make(string); 21 | } 22 | return Utils.toColor((Utils.PLACEHOLDER_SUPPORT && user != null) ? 23 | Utils.getWithPlaceholders(string, user.toPlayer()) : 24 | string); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/hologram/replacer/RGBLine.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.hologram.replacer; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | import net.md_5.bungee.api.ChatColor; 5 | 6 | import java.util.concurrent.ThreadLocalRandom; 7 | 8 | public class RGBLine implements LineReplacer { 9 | private static final char HEX_COLOR_CHAR = '#'; 10 | 11 | private static final int HEX_COLOR_LENGTH = 6; 12 | 13 | public String make(String string) { 14 | String rgbString = string; 15 | for (int i = 0; i < rgbString.length(); i++) { 16 | char charAt = rgbString.charAt(i); 17 | if (charAt == '#') { 18 | int endIndex = i + 6 + 1; 19 | boolean success = true; 20 | StringBuilder hexCodeStringBuilder = new StringBuilder(); 21 | for (int i2 = i; i2 < endIndex; i2++) { 22 | if (rgbString.length() - 1 < i2) { 23 | success = false; 24 | break; 25 | } 26 | char hexCode = rgbString.charAt(i2); 27 | hexCodeStringBuilder.append(Character.valueOf(hexCode)); 28 | } 29 | if (success) 30 | try { 31 | rgbString = rgbString.substring(0, i) + ChatColor.of(hexCodeStringBuilder.toString()) + rgbString.substring(endIndex); 32 | } catch (Exception exception) {} 33 | } 34 | } 35 | return rgbString; 36 | } 37 | 38 | public boolean isSupported() { 39 | return (Utils.BUKKIT_VERSION > 15); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/Packet.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.mojang.authlib.GameProfile; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import io.github.gonalez.znpcs.npc.FunctionFactory; 7 | import io.github.gonalez.znpcs.npc.ItemSlot; 8 | import io.github.gonalez.znpcs.npc.NPC; 9 | import io.github.gonalez.znpcs.npc.NPCType; 10 | import io.github.gonalez.znpcs.utility.ReflectionUtils; 11 | import io.github.gonalez.znpcs.utility.Utils; 12 | import org.bukkit.inventory.ItemStack; 13 | 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | 17 | public interface Packet { 18 | int version(); 19 | 20 | @PacketValue(keyName = "playerPacket") 21 | Object getPlayerPacket(Object paramObject, GameProfile paramGameProfile) throws ReflectiveOperationException; 22 | 23 | @PacketValue(keyName = "spawnPacket") 24 | Object getSpawnPacket(Object paramObject, boolean paramBoolean) throws ReflectiveOperationException; 25 | 26 | Object convertItemStack(int paramInt, ItemSlot paramItemSlot, ItemStack paramItemStack) throws ReflectiveOperationException; 27 | 28 | Object getClickType(Object paramObject) throws ReflectiveOperationException; 29 | 30 | Object getMetadataPacket(int paramInt, Object paramObject) throws ReflectiveOperationException; 31 | 32 | @PacketValue(keyName = "hologramSpawnPacket", valueType = ValueType.ARGUMENTS) 33 | Object getHologramSpawnPacket(Object paramObject) throws ReflectiveOperationException; 34 | 35 | @PacketValue(keyName = "destroyPacket", valueType = ValueType.ARGUMENTS) 36 | default Object getDestroyPacket(int entityId) throws ReflectiveOperationException { 37 | return CacheRegistry.PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR.load().newInstance( 38 | CacheRegistry.PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR.load().getParameterTypes()[0].isArray() ? new int[]{entityId} : entityId); 39 | } 40 | 41 | @PacketValue(keyName = "enumSlot", valueType = ValueType.ARGUMENTS) 42 | default Object getItemSlot(int slot) { 43 | return CacheRegistry.ENUM_ITEM_SLOT.getEnumConstants()[slot]; 44 | } 45 | 46 | @PacketValue(keyName = "removeTab") 47 | default Object getTabRemovePacket(Object nmsEntity) throws ReflectiveOperationException { 48 | try { 49 | return CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load().newInstance(CacheRegistry.REMOVE_PLAYER_FIELD 50 | .load(), 51 | Collections.singletonList(nmsEntity)); 52 | } catch (Throwable throwable) { 53 | boolean useOldMethod = CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CLASS != null; 54 | if (useOldMethod) { 55 | return CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CONSTRUCTOR.load() 56 | .newInstance(Collections.singletonList(CacheRegistry.GET_UNIQUE_ID_METHOD.load().invoke(nmsEntity))); 57 | } else { 58 | return CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load().newInstance(CacheRegistry.REMOVE_PLAYER_FIELD 59 | .load(), 60 | nmsEntity); 61 | } 62 | } 63 | } 64 | 65 | @PacketValue(keyName = "equipPackets") 66 | ImmutableList getEquipPackets(NPC paramNPC) throws ReflectiveOperationException; 67 | 68 | @PacketValue(keyName = "scoreboardPackets") 69 | default ImmutableList updateScoreboard(NPC npc) throws ReflectiveOperationException { 70 | ImmutableList.Builder builder = ImmutableList.builder(); 71 | boolean isVersion17 = (Utils.BUKKIT_VERSION > 16); 72 | boolean isVersion9 = (Utils.BUKKIT_VERSION > 8); 73 | Object scoreboardTeamPacket = isVersion17 ? CacheRegistry.SCOREBOARD_TEAM_CONSTRUCTOR.load().newInstance(null, npc.getGameProfile().getName()) : CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CONSTRUCTOR_OLD.load().newInstance(); 74 | if (!isVersion17) { 75 | Utils.setValue(scoreboardTeamPacket, "a", npc.getGameProfile().getName()); 76 | Utils.setValue(scoreboardTeamPacket, isVersion9 ? "i" : "h", 1); 77 | } 78 | builder.add(isVersion17 ? CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE_V1.load().invoke(null, scoreboardTeamPacket) : scoreboardTeamPacket); 79 | if (isVersion17) { 80 | scoreboardTeamPacket = CacheRegistry.SCOREBOARD_TEAM_CONSTRUCTOR.load().newInstance(null, npc.getGameProfile().getName()); 81 | if (Utils.BUKKIT_VERSION > 17) { 82 | Utils.setValue(scoreboardTeamPacket, "d", npc.getGameProfile().getName()); 83 | ReflectionUtils.findFieldForClassAndSet(scoreboardTeamPacket, CacheRegistry.ENUM_TAG_VISIBILITY, CacheRegistry.ENUM_TAG_VISIBILITY_NEVER_FIELD.load()); 84 | Utils.setValue(scoreboardTeamPacket, "m", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, "DARK_GRAY")); 85 | } else { 86 | Utils.setValue(scoreboardTeamPacket, "e", npc.getGameProfile().getName()); 87 | Utils.setValue(scoreboardTeamPacket, "l", CacheRegistry.ENUM_TAG_VISIBILITY_NEVER_FIELD.load()); 88 | } 89 | } else { 90 | scoreboardTeamPacket = CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CONSTRUCTOR_OLD.load().newInstance(); 91 | Utils.setValue(scoreboardTeamPacket, "a", npc.getGameProfile().getName()); 92 | Utils.setValue(scoreboardTeamPacket, "e", "never"); 93 | Utils.setValue(scoreboardTeamPacket, isVersion9 ? "i" : "h", 0); 94 | } 95 | Collection collection = (Collection) (isVersion17 ? 96 | CacheRegistry.SCOREBOARD_PLAYER_LIST.load().invoke(scoreboardTeamPacket) : Utils.getValue(scoreboardTeamPacket, isVersion9 ? "h" : "g")); 97 | if (npc.getNpcPojo().getNpcType() == NPCType.PLAYER) { 98 | collection.add(npc.getGameProfile().getName()); 99 | } else { 100 | collection.add(npc.getUUID().toString()); 101 | } 102 | if (allowGlowColor() && FunctionFactory.isTrue(npc, "glow")) 103 | updateGlowPacket(npc, scoreboardTeamPacket); 104 | builder.add(isVersion17 ? CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE.load().invoke(null, scoreboardTeamPacket, Boolean.TRUE) : scoreboardTeamPacket); 105 | return builder.build(); 106 | } 107 | 108 | void updateGlowPacket(NPC paramNPC, Object paramObject) throws ReflectiveOperationException; 109 | 110 | boolean allowGlowColor(); 111 | 112 | default void update(PacketCache packetCache) throws ReflectiveOperationException { 113 | packetCache.flushCache("scoreboardPackets"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketCache.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Proxy; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | public class PacketCache { 14 | protected static final ImmutableMap VALUE_LOOKUP_BY_NAME; 15 | 16 | static { 17 | ImmutableMap.Builder methodPacketValueBuilder = ImmutableMap.builder(); 18 | for (Method method : Packet.class.getMethods()) { 19 | if (method.isAnnotationPresent(PacketValue.class)) 20 | methodPacketValueBuilder.put(method, method.getAnnotation(PacketValue.class)); 21 | } 22 | VALUE_LOOKUP_BY_NAME = methodPacketValueBuilder.build(); 23 | } 24 | 25 | private final Map packetResultCache = new ConcurrentHashMap<>(); 26 | 27 | private final Packet proxyInstance; 28 | 29 | public PacketCache(Packet packet) { 30 | this.proxyInstance = newProxyInstance(packet); 31 | } 32 | 33 | public PacketCache() { 34 | this(PacketFactory.PACKET_FOR_CURRENT_VERSION); 35 | } 36 | 37 | public Packet getProxyInstance() { 38 | return this.proxyInstance; 39 | } 40 | 41 | protected Packet newProxyInstance(Packet packet) { 42 | return (Packet)Proxy.newProxyInstance(packet 43 | .getClass().getClassLoader(), new Class[] { Packet.class }, new PacketHandler(this, packet)); 44 | } 45 | 46 | private Object getOrCache(Packet instance, Method method, Object[] args) { 47 | if (!VALUE_LOOKUP_BY_NAME.containsKey(method)) 48 | throw new IllegalStateException("value not found for method: " + method.getName()); 49 | PacketValue packetValue = VALUE_LOOKUP_BY_NAME.get(method); 50 | String keyString = packetValue.valueType().resolve(packetValue.keyName(), args); 51 | return this.packetResultCache.computeIfAbsent(keyString, o -> { 52 | try { 53 | return method.invoke(instance, args); 54 | } catch (InvocationTargetException|IllegalAccessException operationException) { 55 | throw new AssertionError("can't invoke method: " + method.getName(), operationException); 56 | } 57 | }); 58 | } 59 | 60 | public void flushCache(String... strings) { 61 | Set> set = this.packetResultCache.entrySet(); 62 | for (String string : strings) 63 | set.removeIf(entry -> entry.getKey().startsWith(string)); 64 | } 65 | 66 | public void flushCache() { 67 | flushCache(VALUE_LOOKUP_BY_NAME.values().stream() 68 | .map(PacketValue::keyName).toArray(x$0 -> new String[x$0])); 69 | } 70 | 71 | private static class PacketHandler implements InvocationHandler { 72 | private final PacketCache packetCache; 73 | 74 | private final Packet packets; 75 | 76 | public PacketHandler(PacketCache packetCache, Packet packets) { 77 | this.packetCache = packetCache; 78 | this.packets = packets; 79 | } 80 | 81 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 82 | if (PacketCache.VALUE_LOOKUP_BY_NAME.containsKey(method)) 83 | return this.packetCache.getOrCache(this.packets, method, args); 84 | return method.invoke(this.packets, args); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import io.github.gonalez.znpcs.utility.Utils; 5 | 6 | import java.util.Comparator; 7 | 8 | public final class PacketFactory { 9 | public static final ImmutableSet ALL = ImmutableSet.of(new PacketV8(), new PacketV9(), 10 | new PacketV16(), new PacketV17(), new PacketV18(), new PacketV19()); 11 | 12 | public static final Packet PACKET_FOR_CURRENT_VERSION = findPacketForVersion(Utils.BUKKIT_VERSION); 13 | 14 | public static Packet findPacketForVersion(int version) { 15 | return ALL.stream() 16 | .filter(packet -> (version >= packet.version())) 17 | .max(Comparator.comparing(Packet::version)) 18 | .orElseThrow(() -> new IllegalArgumentException("No packet instance found for version: " + version)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV16.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Lists; 5 | import com.mojang.datafixers.util.Pair; 6 | import io.github.gonalez.znpcs.cache.CacheRegistry; 7 | import io.github.gonalez.znpcs.npc.ItemSlot; 8 | import io.github.gonalez.znpcs.npc.NPC; 9 | import org.bukkit.inventory.ItemStack; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class PacketV16 extends PacketV9 { 15 | public int version() { 16 | return 16; 17 | } 18 | 19 | public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { 20 | List> pairs = Lists.newArrayListWithCapacity((ItemSlot.values()).length); 21 | for (Map.Entry entry : npc.getNpcPojo().getNpcEquip().entrySet()) 22 | pairs.add(new Pair<>(getItemSlot(entry 23 | .getKey().getSlot()), 24 | convertItemStack(npc.getEntityID(), entry.getKey(), entry.getValue()))); 25 | return ImmutableList.of(CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_V1.load().newInstance(npc.getEntityID(), pairs)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV17.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import io.github.gonalez.znpcs.cache.CacheRegistry; 5 | import io.github.gonalez.znpcs.npc.NPC; 6 | import io.github.gonalez.znpcs.utility.Utils; 7 | import org.bukkit.Bukkit; 8 | 9 | public class PacketV17 extends PacketV16 { 10 | public int version() { 11 | return 17; 12 | } 13 | 14 | public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { 15 | return CacheRegistry.PLAYER_CONSTRUCTOR_NEW.load().newInstance(CacheRegistry.GET_SERVER_METHOD.load().invoke(Bukkit.getServer()), nmsWorld, gameProfile); 16 | } 17 | 18 | public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { 19 | Utils.setValue(packet, "n", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, npc.getNpcPojo().getGlowName())); 20 | } 21 | 22 | public Object getClickType(Object interactPacket) { 23 | return "INTERACT"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV18.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import io.github.gonalez.znpcs.cache.CacheRegistry; 4 | import io.github.gonalez.znpcs.npc.NPC; 5 | import io.github.gonalez.znpcs.utility.Utils; 6 | 7 | public class PacketV18 extends PacketV17 { 8 | 9 | @Override 10 | public int version() { 11 | return 18; 12 | } 13 | 14 | public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { 15 | Utils.setValue(packet, "m", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, npc.getNpcPojo().getGlowName())); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV19.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import io.github.gonalez.znpcs.cache.CacheRegistry; 5 | import org.bukkit.Bukkit; 6 | 7 | public class PacketV19 extends PacketV18 { 8 | public int version() { 9 | return 19; 10 | } 11 | 12 | 13 | public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { 14 | try { 15 | return CacheRegistry.PLAYER_CONSTRUCTOR_NEW_1.load().newInstance(CacheRegistry.GET_SERVER_METHOD 16 | .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile, null); 17 | } catch (Throwable e) { 18 | return CacheRegistry.PLAYER_CONSTRUCTOR_NEW_2.load().newInstance(CacheRegistry.GET_SERVER_METHOD 19 | .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV8.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.mojang.authlib.GameProfile; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import io.github.gonalez.znpcs.npc.ItemSlot; 7 | import io.github.gonalez.znpcs.npc.NPC; 8 | import io.github.gonalez.znpcs.utility.Utils; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.inventory.ItemStack; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.util.Map; 14 | 15 | public class PacketV8 implements Packet { 16 | public int version() { 17 | return 8; 18 | } 19 | 20 | public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { 21 | Constructor constructor = (Utils.BUKKIT_VERSION > 13) ? CacheRegistry.PLAYER_INTERACT_MANAGER_NEW_CONSTRUCTOR.load() : CacheRegistry.PLAYER_INTERACT_MANAGER_OLD_CONSTRUCTOR.load(); 22 | return CacheRegistry.PLAYER_CONSTRUCTOR_OLD.load().newInstance(CacheRegistry.GET_SERVER_METHOD 23 | .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile, constructor 24 | 25 | .newInstance(nmsWorld)); 26 | } 27 | 28 | public Object getSpawnPacket(Object nmsEntity, boolean isPlayer) throws ReflectiveOperationException { 29 | return isPlayer ? CacheRegistry.PACKET_PLAY_OUT_NAMED_ENTITY_CONSTRUCTOR.load().newInstance(nmsEntity) 30 | : CacheRegistry.PACKET_PLAY_OUT_SPAWN_ENTITY_CONSTRUCTOR.load().newInstance(nmsEntity); 31 | } 32 | 33 | public Object convertItemStack(int entityId, ItemSlot itemSlot, ItemStack itemStack) throws ReflectiveOperationException { 34 | return CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_OLD.load().newInstance(entityId, 35 | itemSlot.getSlotOld(), CacheRegistry.AS_NMS_COPY_METHOD 36 | .load().invoke(CacheRegistry.CRAFT_ITEM_STACK_CLASS, itemStack)); 37 | } 38 | 39 | public Object getClickType(Object interactPacket) throws ReflectiveOperationException { 40 | return Utils.getValue(interactPacket, "action"); 41 | } 42 | 43 | public Object getMetadataPacket(int entityId, Object nmsEntity) throws ReflectiveOperationException { 44 | Object dataWatcher = CacheRegistry.GET_DATA_WATCHER_METHOD.load().invoke(nmsEntity); 45 | try { 46 | return CacheRegistry.PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR.load().newInstance( 47 | entityId, dataWatcher, true); 48 | } catch (Exception e2) { 49 | return CacheRegistry.PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR_V1 50 | .load() 51 | .newInstance(entityId, 52 | CacheRegistry.GET_DATAWATCHER_B_LIST.load().invoke(dataWatcher)); 53 | } 54 | } 55 | 56 | public Object getHologramSpawnPacket(Object armorStand) throws ReflectiveOperationException { 57 | return CacheRegistry.PACKET_PLAY_OUT_SPAWN_ENTITY_CONSTRUCTOR.load().newInstance(armorStand); 58 | } 59 | 60 | public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { 61 | ImmutableList.Builder builder = ImmutableList.builder(); 62 | for (Map.Entry stackEntry : npc.getNpcPojo().getNpcEquip().entrySet()) { 63 | builder.add(CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_OLD.load().newInstance(npc.getEntityID(), 64 | stackEntry.getKey().getSlotOld(), 65 | convertItemStack(npc.getEntityID(), stackEntry.getKey(), stackEntry.getValue()))); 66 | } 67 | return builder.build(); 68 | } 69 | 70 | public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { 71 | throw new IllegalStateException("Glow color is not supported for 1.8 version."); 72 | } 73 | 74 | public boolean allowGlowColor() { 75 | return false; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketV9.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import io.github.gonalez.znpcs.cache.CacheRegistry; 5 | import io.github.gonalez.znpcs.npc.ItemSlot; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.utility.Utils; 8 | import org.bukkit.inventory.ItemStack; 9 | 10 | import java.util.Map; 11 | 12 | public class PacketV9 extends PacketV8 { 13 | public int version() { 14 | return 9; 15 | } 16 | 17 | public Object convertItemStack(int entityId, ItemSlot itemSlot, ItemStack itemStack) throws ReflectiveOperationException { 18 | return CacheRegistry.AS_NMS_COPY_METHOD.load().invoke(CacheRegistry.CRAFT_ITEM_STACK_CLASS, itemStack); 19 | } 20 | 21 | public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { 22 | ImmutableList.Builder builder = ImmutableList.builder(); 23 | for (Map.Entry stackEntry : npc.getNpcPojo().getNpcEquip().entrySet()) { 24 | builder.add(CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_NEWEST_OLD.load().newInstance(npc.getEntityID(), 25 | getItemSlot(stackEntry.getKey().getSlot()), 26 | convertItemStack(npc.getEntityID(), stackEntry.getKey(), stackEntry.getValue()))); 27 | } 28 | return builder.build(); 29 | } 30 | 31 | public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { 32 | Object enumChatString = CacheRegistry.ENUM_CHAT_TO_STRING_METHOD.load().invoke(npc.getGlowColor()); 33 | if (Utils.BUKKIT_VERSION > 12) { 34 | Utils.setValue(packet, npc.getGlowColor(), CacheRegistry.ENUM_CHAT_CLASS); 35 | Utils.setValue(packet, "c", CacheRegistry.I_CHAT_BASE_COMPONENT_A_CONSTRUCTOR.load().newInstance(enumChatString)); 36 | } else { 37 | Utils.setValue(packet, "g", CacheRegistry.GET_ENUM_CHAT_ID_METHOD.load().invoke(npc.getGlowColor())); 38 | Utils.setValue(packet, "c", enumChatString); 39 | } 40 | } 41 | 42 | public boolean allowGlowColor() { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/PacketValue.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.METHOD}) 10 | public @interface PacketValue { 11 | String keyName(); 12 | 13 | ValueType valueType() default ValueType.DEFAULT; 14 | } 15 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/packet/ValueType.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.packet; 2 | 3 | import java.util.Arrays; 4 | 5 | public enum ValueType { 6 | ARGUMENTS { 7 | String resolve(String keyName, Object[] args) { 8 | if (args.length == 0) 9 | throw new IllegalArgumentException("invalid size, must be > 0"); 10 | return keyName + Arrays.hashCode(args); 11 | } 12 | }, 13 | DEFAULT { 14 | String resolve(String keyName, Object[] args) { 15 | return keyName; 16 | } 17 | }; 18 | 19 | abstract String resolve(String paramString, Object[] paramArrayOfObject); 20 | } 21 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/task/NPCLoadTask.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.task; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import io.github.gonalez.znpcs.npc.NPC; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.World; 7 | import org.bukkit.scheduler.BukkitRunnable; 8 | 9 | public class NPCLoadTask extends BukkitRunnable { 10 | private static final int DELAY = 40; 11 | 12 | private static final int MAX_TRIES = 10; 13 | 14 | private final NPC npc; 15 | 16 | private int tries = 0; 17 | 18 | public NPCLoadTask(NPC npc) { 19 | this.npc = npc; 20 | ServersNPC.SCHEDULER.runTaskTimer(this, 40); 21 | } 22 | 23 | public void run() { 24 | if (this.tries++ > 10) { 25 | cancel(); 26 | return; 27 | } 28 | World world = Bukkit.getWorld(this.npc.getNpcPojo().getLocation().getWorldName()); 29 | if (world == null) 30 | return; 31 | cancel(); 32 | this.npc.onLoad(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/task/NPCManagerTask.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.task; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import io.github.gonalez.znpcs.ZNPConfigUtils; 5 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 6 | import io.github.gonalez.znpcs.npc.FunctionFactory; 7 | import io.github.gonalez.znpcs.npc.NPC; 8 | import io.github.gonalez.znpcs.npc.conversation.ConversationModel; 9 | import io.github.gonalez.znpcs.user.ZUser; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.scheduler.BukkitRunnable; 13 | 14 | public class NPCManagerTask extends BukkitRunnable { 15 | public NPCManagerTask(ServersNPC serversNPC) { 16 | runTaskTimerAsynchronously(serversNPC, 60L, 1L); 17 | } 18 | 19 | public void run() { 20 | for (NPC npc : NPC.all()) { 21 | boolean hasPath = (npc.getNpcPath() != null); 22 | if (hasPath) 23 | npc.getNpcPath().handle(); 24 | for (Player player : Bukkit.getOnlinePlayers()) { 25 | ZUser zUser = ZUser.find(player); 26 | boolean canSeeNPC = (player.getWorld() == npc.getLocation().getWorld() 27 | && player.getLocation().distance(npc.getLocation()) <= ZNPConfigUtils.getConfig(ConfigConfiguration.class).viewDistance); 28 | if (npc.getViewers().contains(zUser) && !canSeeNPC) { 29 | npc.delete(zUser); 30 | continue; 31 | } 32 | if (canSeeNPC) { 33 | if (!npc.getViewers().contains(zUser)) 34 | npc.spawn(zUser); 35 | if (FunctionFactory.isTrue(npc, "look") && !hasPath) 36 | npc.lookAt(zUser, player.getLocation(), false); 37 | npc.getHologram().updateNames(zUser); 38 | ConversationModel conversationStorage = npc.getNpcPojo().getConversation(); 39 | if (conversationStorage != null && conversationStorage.getConversationType() == ConversationModel.ConversationType.RADIUS) 40 | npc.tryStartConversation(player); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/npc/task/NpcRefreshSkinTask.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.npc.task; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.mojang.authlib.GameProfile; 5 | import io.github.gonalez.znpcs.npc.NPC; 6 | import io.github.gonalez.znpcs.npc.NPCSkin; 7 | import io.github.gonalez.znpcs.skin.ApplySkinFetcherListener; 8 | import io.github.gonalez.znpcs.skin.SkinFetcher; 9 | import io.github.gonalez.znpcs.skin.SkinFetcherListener; 10 | import io.github.gonalez.znpcs.utility.PlaceholderUtils; 11 | import org.bukkit.scheduler.BukkitRunnable; 12 | 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | public class NpcRefreshSkinTask extends BukkitRunnable { 17 | private final Set outgoingRefresh = new HashSet<>(); 18 | private final SkinFetcher skinFetcher; 19 | 20 | private int count = 0; 21 | 22 | public NpcRefreshSkinTask(SkinFetcher skinFetcher) { 23 | this.skinFetcher = Preconditions.checkNotNull(skinFetcher); 24 | } 25 | 26 | @Override 27 | public void run() { 28 | for (NPC npc : NPC.all()) { 29 | int refreshSkinDuration = npc.getNpcPojo().getRefreshSkinDuration(); 30 | if (refreshSkinDuration != 0 && count % refreshSkinDuration == 0) { 31 | int id = npc.getNpcPojo().getId(); 32 | if (outgoingRefresh.contains(id)) { 33 | continue; 34 | } 35 | outgoingRefresh.add(id); 36 | skinFetcher.fetchGameProfile( 37 | PlaceholderUtils.formatPlaceholders(npc.getNpcPojo().getSkinName()), 38 | new ApplySkinFetcherListener(npc) { 39 | @Override 40 | public void onComplete(GameProfile gameProfile) { 41 | outgoingRefresh.remove(id); 42 | super.onComplete(gameProfile); 43 | } 44 | 45 | @Override 46 | public void onError(Throwable error) { 47 | outgoingRefresh.remove(id); 48 | super.onError(error); 49 | } 50 | }); 51 | } 52 | } 53 | count++; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/skin/ApplySkinFetcherListener.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.mojang.authlib.GameProfile; 5 | import com.mojang.authlib.properties.Property; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.npc.NPCSkin; 8 | 9 | public abstract class ApplySkinFetcherListener implements SkinFetcherListener { 10 | private final NPC npc; 11 | 12 | public ApplySkinFetcherListener(NPC npc) { 13 | this.npc = Preconditions.checkNotNull(npc); 14 | } 15 | 16 | @Override 17 | public void onComplete(GameProfile gameProfile) { 18 | Property textureProperty = GameProfiles.getTextureProperty(gameProfile); 19 | if (textureProperty != null) { 20 | npc.changeSkin(NPCSkin.forValues( 21 | textureProperty.getValue(), textureProperty.getSignature())); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/user/EventService.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.user; 2 | 3 | import io.github.gonalez.znpcs.ServersNPC; 4 | import org.bukkit.event.Event; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.function.Consumer; 10 | 11 | public class EventService { 12 | private final Class eventClass; 13 | 14 | private final List> eventConsumers; 15 | 16 | protected EventService(Class eventClass, List> eventConsumers) { 17 | this.eventClass = eventClass; 18 | this.eventConsumers = eventConsumers; 19 | } 20 | 21 | public Class getEventClass() { 22 | return this.eventClass; 23 | } 24 | 25 | public List> getEventConsumers() { 26 | return this.eventConsumers; 27 | } 28 | 29 | public EventService addConsumer(Consumer consumer) { 30 | getEventConsumers().add(consumer); 31 | return this; 32 | } 33 | 34 | 35 | public void runAll(T event) { 36 | ServersNPC.SCHEDULER.runTask(() -> { 37 | this.eventConsumers.forEach((consumer) -> { 38 | consumer.accept(event); 39 | }); 40 | }); 41 | } 42 | 43 | 44 | public static EventService addService(ZUser user, Class eventClass) { 45 | if (hasService(user, eventClass)) 46 | throw new IllegalStateException(eventClass.getSimpleName() + " is already register for " + user.getUUID().toString()); 47 | EventService service = new EventService<>(eventClass, new ArrayList<>()); 48 | user.getEventServices().add(service); 49 | user.toPlayer().closeInventory(); 50 | return service; 51 | } 52 | 53 | public static EventService findService(ZUser user, Class eventClass) { 54 | Objects.requireNonNull(EventService.class); 55 | return user.getEventServices().stream().filter(eventService -> eventService.getEventClass().isAssignableFrom(eventClass)).map(EventService.class::cast) 56 | .findFirst() 57 | .orElse(null); 58 | } 59 | 60 | public static boolean hasService(ZUser user, Class eventClass) { 61 | return user.getEventServices() 62 | .stream() 63 | .anyMatch(eventService -> (eventService.getEventClass() == eventClass)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/user/NpcInteractServerHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.user; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.base.Stopwatch; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.npc.NPCAction; 8 | import io.github.gonalez.znpcs.npc.NPCModel; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInboundHandlerAdapter; 11 | import java.io.Serializable; 12 | import java.util.HashMap; 13 | import java.util.Objects; 14 | import java.util.concurrent.TimeUnit; 15 | import javax.annotation.concurrent.GuardedBy; 16 | 17 | /** A handler that responds to NPC interact messages. */ 18 | final class NpcInteractServerHandler extends ChannelInboundHandlerAdapter { 19 | private static final long DEFAULT_NPC_INTERACT_INTERVAL_MS = 5_000; 20 | 21 | final Object lock = new Object(); 22 | 23 | @GuardedBy("lock") 24 | final HashMap npcClickTimers = new HashMap<>(); 25 | 26 | /** A npc/value pair used in for tracking npc click cooldowns. */ 27 | private static final class NPCValuePair implements Serializable { 28 | final NPC npc; 29 | final Object value; 30 | 31 | private NPCValuePair(NPC npc, Object value) { 32 | this.npc = npc; 33 | this.value = value; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || getClass() != o.getClass()) { 42 | return false; 43 | } 44 | NPCValuePair npcValuePair = (NPCValuePair) o; 45 | return npc == npcValuePair.npc && Objects.equals(value, npcValuePair.value); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(npc, value); 51 | } 52 | } 53 | 54 | final ZUser user; 55 | 56 | public NpcInteractServerHandler(ZUser user) { 57 | this.user = Preconditions.checkNotNull(user); 58 | } 59 | 60 | /** Attempts to start or retrieve a timer for the given NPC and associated value. */ 61 | Stopwatch tryStartTiming(NPC npc, Object value) { 62 | NPCValuePair npcValuePair = new NPCValuePair(npc, value); 63 | synchronized (lock) { 64 | Stopwatch stopwatch = npcClickTimers.get(npcValuePair); 65 | if (stopwatch == null) { 66 | npcClickTimers.put(npcValuePair, stopwatch = Stopwatch.createStarted()); 67 | } 68 | return stopwatch; 69 | } 70 | } 71 | 72 | /** 73 | * Resets the timer for the given NPC and value if the specified duration has elapsed. 74 | * If the elapsed time is greater than the provided duration, the timer is reset and 75 | * started again. 76 | * 77 | *

If the timer has not yet expired, the method returns {@code true} to indicate that the 78 | * action should not be reset. Otherwise, it returns {@code false}, signaling that the timer has 79 | * expired and was reset. 80 | */ 81 | boolean resetTimerIfExpired(NPC npc, Object value, long millis) { 82 | Stopwatch stopwatch = tryStartTiming(npc, value); 83 | if (stopwatch.elapsed(TimeUnit.MILLISECONDS) > millis) { 84 | stopwatch.reset().start(); 85 | return false; 86 | } 87 | return true; 88 | } 89 | 90 | /** 91 | * Handles incoming packets related to Entity interaction. When a packet of type {@link 92 | * CacheRegistry#PACKET_PLAY_IN_USE_ENTITY_CLASS} is received, it processes the entity ID 93 | * and checks if the NPC associated with that entity has any registered actions to perform. 94 | * 95 | *

Note: If the packet is not of the expected type, the method simply passes the 96 | * message to the next handler in the pipeline for further processing. 97 | * 98 | * @param ctx the context for this handler. 99 | * @param msg the incoming message. 100 | */ 101 | @Override 102 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 103 | if (CacheRegistry.PACKET_PLAY_IN_USE_ENTITY_CLASS.isInstance(msg)) { 104 | int entityId = CacheRegistry.PACKET_IN_USE_ENTITY_ID_FIELD.load().getInt(msg); 105 | for (NPC npc : NPC.all()) { 106 | NPCModel npcModel = npc.getNpcPojo(); 107 | if (npc.getEntityID() != entityId) { 108 | continue; 109 | } 110 | if (resetTimerIfExpired(npc, npc.getEntityID(), DEFAULT_NPC_INTERACT_INTERVAL_MS)) { 111 | return; 112 | } 113 | for (NPCAction npcAction : npcModel.getClickActions()) { 114 | if (npcAction.getDelay() > 0) { 115 | if (resetTimerIfExpired(npc, npcAction, npcAction.getDelay() * 1000L)) { 116 | return; 117 | } 118 | } 119 | npcAction.run(user, npcAction.getAction()); 120 | } 121 | } 122 | } else { 123 | ctx.fireChannelRead(msg); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/user/ZUser.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.user; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import io.github.gonalez.znpcs.ServersNPC; 5 | import io.github.gonalez.znpcs.cache.CacheRegistry; 6 | import io.github.gonalez.znpcs.npc.NPC; 7 | import io.github.gonalez.znpcs.npc.NPCAction; 8 | import io.github.gonalez.znpcs.npc.event.ClickType; 9 | import io.github.gonalez.znpcs.npc.event.NPCInteractEvent; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.MessageToMessageDecoder; 13 | import org.bukkit.Bukkit; 14 | import org.bukkit.entity.Player; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.UUID; 21 | 22 | public class ZUser { 23 | private static final String CHANNEL_NAME = "npc_interact"; 24 | 25 | private static final int DEFAULT_DELAY = 1; 26 | 27 | private static final Map USER_MAP = new HashMap<>(); 28 | 29 | private final Map lastClicked; 30 | 31 | private final List> eventServices; 32 | 33 | private final UUID uuid; 34 | 35 | private final GameProfile gameProfile; 36 | 37 | private final Object playerConnection; 38 | 39 | private boolean hasPath = false; 40 | 41 | private long lastInteract = 0L; 42 | 43 | public ZUser(UUID uuid) { 44 | this.uuid = uuid; 45 | this.lastClicked = new HashMap<>(); 46 | this.eventServices = new ArrayList<>(); 47 | try { 48 | Object playerHandle = CacheRegistry.GET_HANDLE_PLAYER_METHOD.load().invoke(toPlayer()); 49 | this.gameProfile = (GameProfile) CacheRegistry.GET_PROFILE_METHOD.load().invoke(playerHandle, new Object[0]); 50 | Channel channel = (Channel) CacheRegistry.CHANNEL_FIELD.load().get(CacheRegistry.NETWORK_MANAGER_FIELD.load() 51 | .get(this.playerConnection = CacheRegistry.PLAYER_CONNECTION_FIELD.load().get(playerHandle))); 52 | if (channel.pipeline().names().contains("npc_interact")) 53 | channel.pipeline().remove("npc_interact"); 54 | channel.pipeline().addAfter("decoder", "npc_interact", new NpcInteractServerHandler(this)); 55 | } catch (IllegalAccessException|java.lang.reflect.InvocationTargetException e) { 56 | throw new IllegalStateException("can't create player " + uuid.toString(), e.getCause()); 57 | } 58 | } 59 | 60 | public UUID getUUID() { 61 | return this.uuid; 62 | } 63 | 64 | public GameProfile getGameProfile() { 65 | return this.gameProfile; 66 | } 67 | 68 | public Object getPlayerConnection() { 69 | return this.playerConnection; 70 | } 71 | 72 | public boolean isHasPath() { 73 | return this.hasPath; 74 | } 75 | 76 | public List> getEventServices() { 77 | return this.eventServices; 78 | } 79 | 80 | public void setHasPath(boolean hasPath) { 81 | this.hasPath = hasPath; 82 | } 83 | 84 | public Player toPlayer() { 85 | return Bukkit.getPlayer(this.uuid); 86 | } 87 | 88 | public static ZUser find(UUID uuid) { 89 | return USER_MAP.computeIfAbsent(uuid, ZUser::new); 90 | } 91 | 92 | public static ZUser find(Player player) { 93 | return find(player.getUniqueId()); 94 | } 95 | 96 | public static void unregister(Player player) { 97 | ZUser zUser = USER_MAP.get(player.getUniqueId()); 98 | if (zUser == null) 99 | throw new IllegalStateException("can't find user " + player.getUniqueId()); 100 | USER_MAP.remove(player.getUniqueId()); 101 | NPC.all().stream() 102 | .filter(npc -> npc.getViewers().contains(zUser)) 103 | .forEach(npc -> npc.delete(zUser)); 104 | } 105 | 106 | class ZNPCSocketDecoder extends MessageToMessageDecoder { 107 | protected void decode(ChannelHandlerContext channelHandlerContext, Object packet, List out) throws Exception { 108 | out.add(packet); 109 | if (packet.getClass() == CacheRegistry.PACKET_PLAY_IN_USE_ENTITY_CLASS) { 110 | long lastInteractNanos = System.nanoTime() - ZUser.this.lastInteract; 111 | if (ZUser.this.lastInteract != 0L && lastInteractNanos < 1000000000L) 112 | return; 113 | int entityId = CacheRegistry.PACKET_IN_USE_ENTITY_ID_FIELD.load().getInt(packet); 114 | NPC npc = NPC.all().stream().filter(npc1 -> (npc1.getEntityID() == entityId)).findFirst().orElse(null); 115 | if (npc == null) 116 | return; 117 | ClickType clickName = ClickType.forName(npc.getPackets().getProxyInstance().getClickType(packet).toString()); 118 | ZUser.this.lastInteract = System.nanoTime(); 119 | ServersNPC.SCHEDULER.scheduleSyncDelayedTask(() -> { 120 | Bukkit.getServer().getPluginManager().callEvent(new NPCInteractEvent(ZUser.this.toPlayer(), clickName, npc)); 121 | List actions = npc.getNpcPojo().getClickActions(); 122 | if (actions == null || actions.isEmpty()) 123 | return; 124 | for (NPCAction npcAction : actions) { 125 | if (npcAction.getClickType() != ClickType.DEFAULT && clickName != npcAction.getClickType()) 126 | continue; 127 | if (npcAction.getDelay() > 0) { 128 | int actionId = npc.getNpcPojo().getClickActions().indexOf(npcAction); 129 | if (ZUser.this.lastClicked.containsKey(actionId)) { 130 | long lastClickNanos = System.nanoTime() - ZUser.this.lastClicked.get(actionId); 131 | if (lastClickNanos < npcAction.getFixedDelay()) 132 | continue; 133 | } 134 | ZUser.this.lastClicked.put(actionId, System.nanoTime()); 135 | } 136 | npcAction.run(ZUser.this, npcAction.getAction()); 137 | } 138 | }, 1); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/BungeeUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.plugin.Plugin; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.DataOutputStream; 8 | import java.io.IOException; 9 | 10 | public class BungeeUtils { 11 | private final Plugin plugin; 12 | 13 | public BungeeUtils(Plugin plugin) { 14 | this.plugin = plugin; 15 | } 16 | 17 | public void sendPlayerToServer(Player player, String server) { 18 | ByteArrayOutputStream b = new ByteArrayOutputStream(); 19 | DataOutputStream out = new DataOutputStream(b); 20 | try { 21 | out.writeUTF("Connect"); 22 | out.writeUTF(server); 23 | } catch (IOException e) { 24 | e.printStackTrace(); 25 | } 26 | player.sendPluginMessage(this.plugin, "BungeeCord", b.toByteArray()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/GuavaCollectors.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | 7 | import java.util.function.Function; 8 | import java.util.stream.Collector; 9 | import java.util.stream.Collectors; 10 | 11 | public final class GuavaCollectors { 12 | public static Collector> toImmutableList() { 13 | return Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf); 14 | } 15 | 16 | public static Collector> toImmutableSet() { 17 | return Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf); 18 | } 19 | 20 | public static Collector> toImmutableMap(Function keyFunction, Function valueFunction) { 21 | return Collectors.collectingAndThen(Collectors.toMap(keyFunction, valueFunction), ImmutableMap::copyOf); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/PlaceholderUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import me.clip.placeholderapi.PlaceholderAPI; 4 | import org.bukkit.Bukkit; 5 | 6 | public final class PlaceholderUtils { 7 | 8 | public static String formatPlaceholders(String str) { 9 | return Bukkit.getOnlinePlayers().stream() 10 | .findAny() 11 | .map(player -> { 12 | return PlaceholderAPI.setPlaceholders(player, str); 13 | }).orElse(str); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public final class ReflectionUtils { 6 | public static Field findFieldForClass(Object instance, Class type) { 7 | for (Field field : instance.getClass().getDeclaredFields()) { 8 | if (field.getType() == type) { 9 | field.setAccessible(true); 10 | return field; 11 | } 12 | } 13 | return null; 14 | } 15 | 16 | public static Field findFieldForClassAndSet(Object instance, Class type, Object value) throws ReflectiveOperationException { 17 | Field field = findFieldForClass(instance, type); 18 | if (field == null) 19 | return null; 20 | field.set(instance, value); 21 | return field; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/SchedulerUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.plugin.Plugin; 5 | import org.bukkit.scheduler.BukkitRunnable; 6 | import org.bukkit.scheduler.BukkitTask; 7 | 8 | public class SchedulerUtils { 9 | private final Plugin plugin; 10 | 11 | public SchedulerUtils(Plugin plugin) { 12 | this.plugin = plugin; 13 | } 14 | 15 | public BukkitTask runTaskTimer(BukkitRunnable bukkitRunnable, int delay) { 16 | return runTaskTimer(bukkitRunnable, delay, delay); 17 | } 18 | 19 | public BukkitTask runTaskTimer(BukkitRunnable bukkitRunnable, int delay, int continuousDelay) { 20 | return bukkitRunnable.runTaskTimer(this.plugin, delay, continuousDelay); 21 | } 22 | 23 | public BukkitTask runTaskTimerAsynchronously(Runnable runnable, int delay, int continuousDelay) { 24 | return Bukkit.getScheduler().runTaskTimerAsynchronously(this.plugin, runnable, delay, continuousDelay); 25 | } 26 | 27 | public void scheduleSyncDelayedTask(Runnable runnable, int delay) { 28 | Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, runnable, delay); 29 | } 30 | 31 | public BukkitTask runTask(Runnable runnable) { 32 | return Bukkit.getScheduler().runTask(this.plugin, runnable); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/Utils.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility; 2 | 3 | import io.github.gonalez.znpcs.ZNPConfigUtils; 4 | import io.github.gonalez.znpcs.cache.CacheRegistry; 5 | import io.github.gonalez.znpcs.configuration.ConfigConfiguration; 6 | import io.github.gonalez.znpcs.user.ZUser; 7 | import me.clip.placeholderapi.PlaceholderAPI; 8 | import org.apache.commons.lang.math.NumberUtils; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.ChatColor; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.concurrent.ThreadLocalRandom; 16 | 17 | public final class Utils { 18 | public static final int BUKKIT_VERSION; 19 | public static boolean PLACEHOLDER_SUPPORT = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); 20 | 21 | static { 22 | BUKKIT_VERSION = NumberUtils.toInt(getFormattedBukkitPackage()); 23 | } 24 | 25 | public static void sendMessage(CommandSender commandSender, String message, Object... args) { 26 | commandSender.sendMessage(toColor(String.format(message, args))); 27 | } 28 | 29 | public static boolean versionNewer(int version) { 30 | return (BUKKIT_VERSION >= version); 31 | } 32 | 33 | public static String getBukkitPackage() { 34 | return Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; 35 | } 36 | 37 | public static String getFormattedBukkitPackage() { 38 | String version = getBukkitPackage().replace("v", "").replace("R", ""); 39 | return version.substring(2, version.length() - 2); 40 | } 41 | 42 | public static String toColor(String string) { 43 | return ChatColor.translateAlternateColorCodes('&', string); 44 | } 45 | 46 | public static String getWithPlaceholders(String string, Player player) { 47 | return PlaceholderAPI.setPlaceholders(player, string).replace( 48 | ZNPConfigUtils.getConfig(ConfigConfiguration.class).replaceSymbol, " "); 49 | } 50 | 51 | public static String randomString(int length) { 52 | StringBuilder stringBuilder = new StringBuilder(); 53 | for (int index = 0; index < length; index++) 54 | stringBuilder.append(ThreadLocalRandom.current().nextInt(0, 9)); 55 | return stringBuilder.toString(); 56 | } 57 | 58 | public static void sendTitle(Player player, String title, String subTitle) { 59 | player.sendTitle(toColor(title), toColor(subTitle)); 60 | } 61 | 62 | public static void setValue(Object fieldInstance, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { 63 | Field f = fieldInstance.getClass().getDeclaredField(fieldName); 64 | f.setAccessible(true); 65 | f.set(fieldInstance, value); 66 | } 67 | 68 | public static void setValue( 69 | Object fieldInstance, Object value, Class expectedType) 70 | throws NoSuchFieldException, IllegalAccessException { 71 | for (Field field : fieldInstance.getClass().getDeclaredFields()) { 72 | if (field.getType() == expectedType) 73 | setValue(fieldInstance, field.getName(), value); 74 | } 75 | } 76 | 77 | public static Object getValue(Object instance, String fieldName) throws NoSuchFieldException, IllegalAccessException { 78 | Field f = instance.getClass().getDeclaredField(fieldName); 79 | f.setAccessible(true); 80 | return f.get(instance); 81 | } 82 | 83 | public static void sendPackets(ZUser user, Object... packets) { 84 | try { 85 | for (Object packet : packets) { 86 | if (packet != null) 87 | CacheRegistry.SEND_PACKET_METHOD.load().invoke(user.getPlayerConnection(), packet); 88 | } 89 | } catch (IllegalAccessException|java.lang.reflect.InvocationTargetException e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/inventory/ZInventory.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.inventory; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.inventory.Inventory; 7 | 8 | public class ZInventory { 9 | private static final int MAX_ROWS = 6; 10 | 11 | private final Player player; 12 | 13 | private ZInventoryPage page; 14 | 15 | private Inventory inventory; 16 | 17 | public ZInventory(Player player) { 18 | this.player = player; 19 | } 20 | 21 | public Player getPlayer() { 22 | return this.player; 23 | } 24 | 25 | public ZInventoryPage getPage() { 26 | return this.page; 27 | } 28 | 29 | public Inventory getInventory() { 30 | return this.inventory; 31 | } 32 | 33 | public void setCurrentPage(ZInventoryPage page) { 34 | this.page = page; 35 | } 36 | 37 | public Inventory build(ZInventoryPage page) { 38 | if (page == null) 39 | throw new IllegalStateException("page is null"); 40 | if (page.getRows() / 9 > 6) 41 | throw new IllegalArgumentException(String.format("Unexpected rows size. Has %d, max %d", page.getRows(), 6)); 42 | setCurrentPage(page); 43 | page.getInventoryItems().removeIf(zInventoryItem -> !zInventoryItem.isDefault()); 44 | page.update(); 45 | this.inventory = Bukkit.createInventory(new ZInventoryHolder(this), page.getRows(), Utils.toColor(page.getPageName())); 46 | page.getInventoryItems().forEach(zInventoryItem -> this.inventory.setItem(zInventoryItem.getSlot(), zInventoryItem.getItemStack())); 47 | return this.inventory; 48 | } 49 | 50 | public Inventory build() { 51 | return build(this.page); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/inventory/ZInventoryCallback.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.inventory; 2 | 3 | import org.bukkit.event.inventory.InventoryClickEvent; 4 | 5 | public interface ZInventoryCallback { 6 | void onClick(InventoryClickEvent paramInventoryClickEvent); 7 | } 8 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/inventory/ZInventoryHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.inventory; 2 | 3 | import org.bukkit.inventory.Inventory; 4 | import org.bukkit.inventory.InventoryHolder; 5 | 6 | public class ZInventoryHolder implements InventoryHolder { 7 | private final ZInventory zInventory; 8 | 9 | public ZInventoryHolder(ZInventory zInventory) { 10 | this.zInventory = zInventory; 11 | } 12 | 13 | public ZInventory getzInventory() { 14 | return this.zInventory; 15 | } 16 | 17 | public Inventory getInventory() { 18 | return this.zInventory.getInventory(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/inventory/ZInventoryItem.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.inventory; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | 5 | public class ZInventoryItem { 6 | private final ItemStack itemStack; 7 | 8 | private final int slot; 9 | 10 | private final boolean isDefault; 11 | 12 | private final ZInventoryCallback clickCallback; 13 | 14 | public ZInventoryItem(ItemStack itemStack, int slot, boolean isDefault, ZInventoryCallback zInventoryCallback) { 15 | this.itemStack = itemStack; 16 | this.slot = slot; 17 | this.isDefault = isDefault; 18 | this.clickCallback = zInventoryCallback; 19 | } 20 | 21 | public ItemStack getItemStack() { 22 | return this.itemStack; 23 | } 24 | 25 | public int getSlot() { 26 | return this.slot; 27 | } 28 | 29 | public boolean isDefault() { 30 | return this.isDefault; 31 | } 32 | 33 | public ZInventoryCallback getInventoryCallback() { 34 | return this.clickCallback; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/inventory/ZInventoryPage.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.inventory; 2 | 3 | import io.github.gonalez.znpcs.utility.itemstack.ItemStackBuilder; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.Material; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.inventory.ItemStack; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public abstract class ZInventoryPage { 13 | private final ZInventory zInventory; 14 | 15 | private final String pageName; 16 | 17 | private final int rows; 18 | 19 | private final List inventoryItems; 20 | 21 | public ZInventoryPage(ZInventory zInventory, String inventoryName, int rows) { 22 | this.zInventory = zInventory; 23 | this.pageName = inventoryName; 24 | this.rows = rows * 9; 25 | this.inventoryItems = new ArrayList<>(); 26 | if (zInventory.getInventory() != null) { 27 | ZInventoryPage zInventoryPage = zInventory.getPage(); 28 | this.addItem(ItemStackBuilder.forMaterial(Material.ARROW) 29 | .setName(ChatColor.GREEN + "Go back") 30 | .setLore(ChatColor.GRAY + "click here...").build(), 31 | this.rows - 9, true, 32 | (event) -> { 33 | zInventory.setCurrentPage(zInventoryPage); 34 | this.openInventory(); 35 | }); 36 | } 37 | 38 | zInventory.setCurrentPage(this); 39 | } 40 | 41 | public ZInventory getInventory() { 42 | return this.zInventory; 43 | } 44 | 45 | public String getPageName() { 46 | return this.pageName; 47 | } 48 | 49 | public int getRows() { 50 | return this.rows; 51 | } 52 | 53 | public List getInventoryItems() { 54 | return this.inventoryItems; 55 | } 56 | 57 | public boolean containsItem(int slot) { 58 | return this.inventoryItems.stream().anyMatch(zInventoryItem -> (zInventoryItem.getSlot() == slot)); 59 | } 60 | 61 | public ZInventoryItem findItem(int slot) { 62 | return this.inventoryItems.stream() 63 | .filter(zInventoryItem -> (zInventoryItem.getSlot() == slot)) 64 | .findFirst() 65 | .orElseThrow(() -> new IllegalStateException("can't find item for slot " + slot)); 66 | } 67 | 68 | public void addItem(ItemStack itemStack, int slot, boolean isDefault, ZInventoryCallback callback) { 69 | this.inventoryItems.add(new ZInventoryItem(itemStack, slot, isDefault, callback)); 70 | } 71 | 72 | public void addItem(ItemStack itemStack, int slot, ZInventoryCallback callback) { 73 | addItem(itemStack, slot, false, callback); 74 | } 75 | 76 | public Player getPlayer() { 77 | return this.zInventory.getPlayer(); 78 | } 79 | 80 | public void openInventory() { 81 | this.zInventory.getPlayer().openInventory(this.zInventory.build()); 82 | } 83 | 84 | public abstract void update(); 85 | } 86 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/itemstack/ItemStackBuilder.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.itemstack; 2 | 3 | import io.github.gonalez.znpcs.utility.Utils; 4 | import org.bukkit.Material; 5 | import org.bukkit.inventory.ItemStack; 6 | import org.bukkit.inventory.meta.ItemMeta; 7 | 8 | import java.util.Arrays; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.StreamSupport; 11 | 12 | public class ItemStackBuilder { 13 | private final ItemStack itemStack; 14 | 15 | private final ItemMeta itemMeta; 16 | 17 | protected ItemStackBuilder(ItemStack stack) { 18 | this.itemStack = stack; 19 | this.itemMeta = stack.getItemMeta(); 20 | } 21 | 22 | public static ItemStackBuilder forMaterial(Material material) { 23 | if (material == null || material == Material.AIR) 24 | throw new IllegalStateException("can't create builder for a NULL material."); 25 | return new ItemStackBuilder(new ItemStack(material, 1)); 26 | } 27 | 28 | public ItemStackBuilder setName(String name) { 29 | this.itemMeta.setDisplayName(Utils.toColor(name)); 30 | return this; 31 | } 32 | 33 | public ItemStackBuilder setLore(Iterable lore) { 34 | this.itemMeta.setLore(StreamSupport.stream(lore.spliterator(), false) 35 | .map(Utils::toColor).collect(Collectors.toList())); 36 | this.itemStack.setItemMeta(this.itemMeta); 37 | return this; 38 | } 39 | 40 | public ItemStackBuilder setLore(String... lore) { 41 | return setLore(Arrays.asList(lore)); 42 | } 43 | 44 | public ItemStackBuilder setAmount(int amount) { 45 | this.itemStack.setAmount(amount); 46 | return this; 47 | } 48 | 49 | public ItemStack build() { 50 | this.itemStack.setItemMeta(this.itemMeta); 51 | return this.itemStack; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/itemstack/ItemStackSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.itemstack; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import com.google.gson.JsonPrimitive; 8 | import com.google.gson.JsonSerializationContext; 9 | import com.google.gson.JsonSerializer; 10 | import org.bukkit.Material; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.bukkit.util.io.BukkitObjectInputStream; 13 | import org.bukkit.util.io.BukkitObjectOutputStream; 14 | 15 | import java.io.ByteArrayInputStream; 16 | import java.io.ByteArrayOutputStream; 17 | import java.io.IOException; 18 | import java.lang.reflect.Type; 19 | import java.util.Base64; 20 | 21 | public class ItemStackSerializer implements JsonSerializer, JsonDeserializer { 22 | private static final ItemStack DEFAULT = new ItemStack(Material.AIR); 23 | 24 | public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 25 | try { 26 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(json.getAsString())); 27 | try { 28 | BukkitObjectInputStream bukkitObjectOutputStream = new BukkitObjectInputStream(byteArrayInputStream); 29 | try { 30 | ItemStack itemStack = (ItemStack)bukkitObjectOutputStream.readObject(); 31 | bukkitObjectOutputStream.close(); 32 | byteArrayInputStream.close(); 33 | return itemStack; 34 | } catch (Throwable throwable) { 35 | try { 36 | bukkitObjectOutputStream.close(); 37 | } catch (Throwable throwable1) { 38 | throwable.addSuppressed(throwable1); 39 | } 40 | throw throwable; 41 | } 42 | } catch (Throwable throwable) { 43 | try { 44 | byteArrayInputStream.close(); 45 | } catch (Throwable throwable1) { 46 | throwable.addSuppressed(throwable1); 47 | } 48 | throw throwable; 49 | } 50 | } catch (IOException|ClassNotFoundException e) { 51 | return DEFAULT; 52 | } 53 | } 54 | 55 | public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) { 56 | try { 57 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 58 | try { 59 | BukkitObjectOutputStream bukkitObjectOutputStream = new BukkitObjectOutputStream(byteArrayOutputStream); 60 | try { 61 | bukkitObjectOutputStream.writeObject(src); 62 | JsonPrimitive jsonPrimitive = new JsonPrimitive(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); 63 | bukkitObjectOutputStream.close(); 64 | byteArrayOutputStream.close(); 65 | return jsonPrimitive; 66 | } catch (Throwable throwable) { 67 | try { 68 | bukkitObjectOutputStream.close(); 69 | } catch (Throwable throwable1) { 70 | throwable.addSuppressed(throwable1); 71 | } 72 | throw throwable; 73 | } 74 | } catch (Throwable throwable) { 75 | try { 76 | byteArrayOutputStream.close(); 77 | } catch (Throwable throwable1) { 78 | throwable.addSuppressed(throwable1); 79 | } 80 | throw throwable; 81 | } 82 | } catch (IOException e) { 83 | throw new JsonParseException("Cannot serialize itemstack", e); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/github/gonalez/znpcs/utility/location/ZLocation.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.utility.location; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | import com.google.gson.JsonParseException; 8 | import com.google.gson.JsonSerializationContext; 9 | import com.google.gson.JsonSerializer; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.Location; 12 | import org.bukkit.util.Vector; 13 | 14 | import java.lang.reflect.Type; 15 | 16 | public class ZLocation { 17 | public static final ZLocationSerializer SERIALIZER = new ZLocationSerializer(); 18 | 19 | private final String worldName; 20 | 21 | private final double x; 22 | 23 | private final double y; 24 | 25 | private final double z; 26 | 27 | private final float yaw; 28 | 29 | private final float pitch; 30 | 31 | private Location bukkitLocation; 32 | 33 | public ZLocation(String worldName, double x, double y, double z, float yaw, float pitch) { 34 | this.worldName = worldName; 35 | this.x = x; 36 | this.y = y; 37 | this.z = z; 38 | this.yaw = yaw; 39 | this.pitch = pitch; 40 | } 41 | 42 | public ZLocation(Location location) { 43 | this(location.getWorld().getName(), location 44 | .getX(), location 45 | .getY(), location 46 | .getZ(), location 47 | .getYaw(), location 48 | .getPitch()); 49 | } 50 | 51 | public String getWorldName() { 52 | return this.worldName; 53 | } 54 | 55 | public double getX() { 56 | return this.x; 57 | } 58 | 59 | public double getY() { 60 | return this.y; 61 | } 62 | 63 | public double getZ() { 64 | return this.z; 65 | } 66 | 67 | public float getYaw() { 68 | return this.yaw; 69 | } 70 | 71 | public float getPitch() { 72 | return this.pitch; 73 | } 74 | 75 | public Location bukkitLocation() { 76 | if (this.bukkitLocation != null) 77 | return this.bukkitLocation; 78 | return this 79 | .bukkitLocation = new Location(Bukkit.getWorld(this.worldName), this.x, this.y, this.z, this.yaw, this.pitch); 80 | } 81 | 82 | public Vector toVector() { 83 | return bukkitLocation().toVector(); 84 | } 85 | 86 | static class ZLocationSerializer implements JsonSerializer, JsonDeserializer { 87 | public JsonElement serialize(ZLocation src, Type typeOfSrc, JsonSerializationContext context) { 88 | JsonObject jsonObject = new JsonObject(); 89 | jsonObject.addProperty("world", src.getWorldName()); 90 | jsonObject.addProperty("x", src.getX()); 91 | jsonObject.addProperty("y", src.getY()); 92 | jsonObject.addProperty("z", src.getZ()); 93 | jsonObject.addProperty("yaw", src.getYaw()); 94 | jsonObject.addProperty("pitch", src.getPitch()); 95 | return jsonObject; 96 | } 97 | 98 | public ZLocation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 99 | JsonObject jsonObject = json.getAsJsonObject(); 100 | return new ZLocation(jsonObject.get("world").getAsString(), jsonObject 101 | .get("x").getAsDouble(), jsonObject 102 | .get("y").getAsDouble(), jsonObject 103 | .get("z").getAsDouble(), jsonObject 104 | .get("yaw").getAsFloat(), jsonObject 105 | .get("pitch").getAsFloat()); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ServersNPC 2 | main: io.github.gonalez.znpcs.ServersNPC 3 | version: 4.1 4 | api-version: 1.13 5 | softdepend: [ PlaceholderAPI ] -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { url = "https://libraries.minecraft.net/" } 3 | } 4 | 5 | dependencies { 6 | api("com.google.code.gson:gson:2.8.9") 7 | api("com.google.guava:guava:31.0-jre") 8 | api("org.yaml:snakeyaml:1.26") 9 | api("com.mojang:authlib:1.5.21") 10 | api("com.mojang:datafixerupper:3.0.25") 11 | } -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/command/Command.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.command; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Iterables; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | public abstract class Command { 10 | 11 | public abstract String getName(); 12 | 13 | protected abstract CommandResult execute( 14 | CommandProvider commandProvider, ImmutableList args); 15 | 16 | protected abstract int getMandatoryArguments(); 17 | 18 | protected Collection getChildren() { 19 | return ImmutableList.of(); 20 | } 21 | 22 | public CommandResult executeCommand( 23 | CommandProvider commandProvider, ImmutableList args) { 24 | CommandResult validateCommandResult = validateCommand(args); 25 | if (validateCommandResult.hasError()) { 26 | return validateCommandResult; 27 | } 28 | List possibleCommands = new ArrayList<>(); 29 | for (int i = 0; i < args.size(); i++) { 30 | Iterable commands = (i == 0) 31 | ? getChildren() 32 | : Iterables.concat(Iterables.transform(possibleCommands, Command::getChildren)); 33 | for (Command command : ImmutableList.copyOf(commands)) { 34 | if (command.getName().startsWith(args.get(i))) { 35 | possibleCommands.add(command); 36 | } 37 | } 38 | } 39 | Command command = possibleCommands.isEmpty() 40 | ? this : possibleCommands.get(possibleCommands.size() - 1); 41 | return command.execute(commandProvider, args); 42 | } 43 | 44 | protected CommandResult newCommandResult() { 45 | return CommandResult.create(this); 46 | } 47 | 48 | CommandResult validateCommand(ImmutableList args) { 49 | CommandResult commandResult = newCommandResult(); 50 | if (args.size() < getMandatoryArguments()) { 51 | commandResult.setErrorMessage("not enough args"); 52 | } 53 | return commandResult; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/command/CommandProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | @FunctionalInterface 6 | public interface CommandProvider { 7 | CommandProvider NOOP = commandClass -> null; 8 | 9 | @Nullable Command provideCommand(Class commandClass); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/command/CommandResult.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.command; 2 | 3 | /** Contains information about the result of {@link Command#execute}. This class is mutable. */ 4 | public final class CommandResult { 5 | public static CommandResult create(Command command) { 6 | return new CommandResult(command); 7 | } 8 | 9 | private Throwable error = null; 10 | private String errorMessage; 11 | 12 | private String successMessage; 13 | 14 | private final Command actualCommand; 15 | 16 | private CommandResult(Command actualCommand) { 17 | this.actualCommand = actualCommand; 18 | } 19 | 20 | public boolean hasError() { 21 | return errorMessage != null || error != null; 22 | } 23 | 24 | public CommandResult setError(Throwable error) { 25 | this.error = error; 26 | return this; 27 | } 28 | 29 | public Throwable getError() { 30 | return error; 31 | } 32 | 33 | public CommandResult setErrorMessage(String errorMessage) { 34 | this.errorMessage = errorMessage; 35 | return this; 36 | } 37 | 38 | public String getErrorMessage() { 39 | return errorMessage; 40 | } 41 | 42 | public CommandResult setSuccessMessage(String successMessage) { 43 | this.successMessage = successMessage; 44 | return this; 45 | } 46 | 47 | public String getSuccessMessage() { 48 | return successMessage; 49 | } 50 | 51 | public Command getActualCommand() { 52 | return actualCommand; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/command/SimpleCommandProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.command; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.ImmutableClassToInstanceMap; 5 | import javax.annotation.Nullable; 6 | 7 | public class SimpleCommandProvider implements CommandProvider { 8 | private final ImmutableClassToInstanceMap commands; 9 | 10 | public SimpleCommandProvider(ImmutableClassToInstanceMap commands) { 11 | this.commands = Preconditions.checkNotNull(commands); 12 | } 13 | 14 | @Nullable 15 | @Override 16 | public Command provideCommand(Class commandClass) { 17 | return commands.get(commandClass); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - Gaston Gonzalez (Gonalez). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.gonalez.znpcs.configuration; 18 | 19 | /** Base class for all configurations. */ 20 | public abstract class Configuration { 21 | 22 | protected Configuration() {} 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/ConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - Gaston Gonzalez (Gonalez). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.gonalez.znpcs.configuration; 18 | 19 | import java.io.IOException; 20 | 21 | @FunctionalInterface 22 | public interface ConfigurationIndex { 23 | T createConfiguration(Class type) throws IOException; 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/ConfigurationKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - Gaston Gonzalez (Gonalez). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.gonalez.znpcs.configuration; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** Indicates a field should be exported for use in a Configuration. */ 26 | @Documented 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Target(ElementType.FIELD) 29 | public @interface ConfigurationKey { 30 | 31 | /** The name by which the annotated field should be identified within the configuration. */ 32 | String name(); 33 | 34 | /** 35 | * A short description of the annotated field, explaining its purpose or significance 36 | * in the configuration. By default, it's empty. 37 | */ 38 | String description() default ""; 39 | } -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/GsonConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.google.gson.JsonParser; 10 | import java.io.Reader; 11 | import java.io.Writer; 12 | import java.lang.reflect.Field; 13 | import java.util.Map; 14 | import java.util.Map.Entry; 15 | 16 | /** A {@link PathConfigurationIndex} that reads and writes configurations from JSON file. */ 17 | public abstract class GsonConfigurationIndex extends PathConfigurationIndex { 18 | private final Gson gson; 19 | 20 | public GsonConfigurationIndex(Gson gson) { 21 | this.gson = checkNotNull(gson); 22 | } 23 | 24 | @Override 25 | protected ImmutableMap readConfiguration( 26 | Reader reader, Class configClass) { 27 | ImmutableMap allFields = getConfigFields(configClass); 28 | ImmutableMap.Builder builder = ImmutableMap.builder(); 29 | JsonElement json = JsonParser.parseReader(reader); 30 | if (json.isJsonObject()) { 31 | mergeFromJsonObject(json.getAsJsonObject(), builder, allFields); 32 | } else { 33 | Map.Entry first = allFields.entrySet().iterator().next(); 34 | builder.put(first.getKey(), gson.fromJson(json, first.getValue().getType())); 35 | } 36 | return builder.build(); 37 | } 38 | 39 | private void mergeFromJsonObject(JsonObject jsonObject, 40 | ImmutableMap.Builder builder, ImmutableMap allFields) { 41 | for (Entry entry : allFields.entrySet()) { 42 | JsonElement element = jsonObject.get(entry.getKey()); 43 | if (element != null) { 44 | builder.put(entry.getKey(), gson.fromJson(element, entry.getValue().getType())); 45 | } 46 | } 47 | } 48 | 49 | @Override 50 | protected void writeConfiguration(Writer writer, ImmutableMap values) { 51 | gson.toJson(values, writer); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/PathConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - Gaston Gonzalez (Gonalez). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.gonalez.znpcs.configuration; 18 | 19 | import com.google.common.cache.CacheBuilder; 20 | import com.google.common.cache.CacheLoader; 21 | import com.google.common.cache.LoadingCache; 22 | import com.google.common.collect.ImmutableMap; 23 | import java.io.BufferedReader; 24 | import java.io.BufferedWriter; 25 | import java.io.IOException; 26 | import java.io.Reader; 27 | import java.io.Writer; 28 | import java.lang.reflect.Field; 29 | import java.lang.reflect.Modifier; 30 | import java.nio.file.Files; 31 | import java.nio.file.Path; 32 | import java.util.LinkedHashMap; 33 | import java.util.Map; 34 | import java.util.Map.Entry; 35 | 36 | public abstract class PathConfigurationIndex implements WritableConfigurationIndex { 37 | 38 | private static final LoadingCache, ImmutableMap> 39 | configurationFieldKeyNameCache = 40 | CacheBuilder.newBuilder() 41 | .build(new CacheLoader<>() { 42 | @Override 43 | public ImmutableMap load(Class configClass) { 44 | Map fields = new LinkedHashMap<>(); 45 | for (Field field : configClass.getDeclaredFields()) { 46 | if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) { 47 | if (!field.isAccessible()) { 48 | field.setAccessible(true); 49 | } 50 | ConfigurationKey key = field.getAnnotation(ConfigurationKey.class); 51 | if (key != null) { 52 | fields.put(key.name(), field); 53 | } 54 | } 55 | } 56 | return ImmutableMap.copyOf(fields); 57 | } 58 | }); 59 | 60 | static ImmutableMap getConfigFields(Class clazz) { 61 | return configurationFieldKeyNameCache.getUnchecked(clazz); 62 | } 63 | 64 | private static ImmutableMap getConfigFieldValues(Configuration configuration) { 65 | ImmutableMap.Builder builder = ImmutableMap.builder(); 66 | for (Entry entry : 67 | configurationFieldKeyNameCache.getUnchecked(configuration.getClass()).entrySet()) { 68 | Field field = entry.getValue(); 69 | try { 70 | Object value = field.get(configuration); 71 | if (value != null) { 72 | builder.put(entry.getKey(), value); 73 | } 74 | } catch (IllegalAccessException e) { 75 | throw new IllegalStateException(e); 76 | } 77 | } 78 | return builder.build(); 79 | } 80 | 81 | @Override 82 | public T createConfiguration(Class type) throws IOException { 83 | T configuration; 84 | try { 85 | configuration = type.newInstance(); 86 | } catch (InstantiationException | IllegalAccessException e) { 87 | throw new IOException("Failed to create config: " + type, e); 88 | } 89 | Path configFilePath = getConfigFilePath(type); 90 | try (BufferedReader reader = Files.newBufferedReader(configFilePath)) { 91 | ImmutableMap values = readConfiguration(reader, type); 92 | for (Entry entry : 93 | configurationFieldKeyNameCache.getUnchecked(type).entrySet()) { 94 | Object test = values.get(entry.getKey()); 95 | if (test != null) { 96 | try { 97 | entry.getValue().set(configuration, test); 98 | } catch (IllegalAccessException e) { 99 | throw new RuntimeException(e); 100 | } 101 | } 102 | } 103 | } 104 | return configuration; 105 | } 106 | 107 | @Override 108 | public void writeConfiguration(Configuration configuration) throws IOException { 109 | Path configFilePath = getConfigFilePath(configuration.getClass()); 110 | try (BufferedWriter writer = Files.newBufferedWriter(configFilePath)) { 111 | writeConfiguration(writer, getConfigFieldValues(configuration)); 112 | } 113 | } 114 | 115 | protected abstract ImmutableMap readConfiguration( 116 | Reader reader, Class configClass) throws IOException; 117 | 118 | protected abstract void writeConfiguration( 119 | Writer writer, ImmutableMap values) throws IOException; 120 | 121 | /** 122 | * Returns the path to the configuration file associated with the specified configuration class. 123 | * 124 | * @param configClass the class of the configuration for which the file path is to be retrieved. 125 | */ 126 | public abstract Path getConfigFilePath(Class configClass); 127 | } 128 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/WritableConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | import java.io.IOException; 4 | 5 | public interface WritableConfigurationIndex extends ConfigurationIndex { 6 | void writeConfiguration(Configuration configuration) throws IOException; 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/configuration/YamlConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.configuration; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.google.common.collect.ImmutableMap; 6 | import java.io.Reader; 7 | import java.io.Writer; 8 | import java.util.Map; 9 | import org.yaml.snakeyaml.Yaml; 10 | 11 | /** A {@link PathConfigurationIndex} that reads and writes configurations from YAML file. */ 12 | public abstract class YamlConfigurationIndex extends PathConfigurationIndex { 13 | private final Yaml yaml; 14 | 15 | public YamlConfigurationIndex(Yaml yaml) { 16 | this.yaml = checkNotNull(yaml); 17 | } 18 | 19 | @Override 20 | protected ImmutableMap readConfiguration( 21 | Reader reader, Class configClass) { 22 | Map data = yaml.load(reader); 23 | return ImmutableMap.copyOf(data); 24 | } 25 | 26 | @Override 27 | protected void writeConfiguration(Writer writer, ImmutableMap values) { 28 | yaml.dump(values, writer); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/AbstractSkinFetcherServer.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParser; 5 | import com.mojang.authlib.GameProfile; 6 | import java.net.http.HttpResponse; 7 | 8 | public abstract class AbstractSkinFetcherServer extends SkinFetcherServer { 9 | 10 | @Override 11 | public GameProfile readProfile(String skinName, HttpResponse httpResponse) { 12 | JsonElement json = JsonParser.parseString(httpResponse.body()); 13 | return readProfile(skinName, json); 14 | } 15 | 16 | protected abstract GameProfile readProfile(String skinName, JsonElement jsonObject); 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/AshconSkinFetcherServer.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.mojang.authlib.GameProfile; 6 | import java.net.URI; 7 | import java.util.UUID; 8 | 9 | public class AshconSkinFetcherServer extends AbstractSkinFetcherServer { 10 | 11 | @Override 12 | protected URI getUriForRequest(String skinName) { 13 | return URI.create(String.format("https://api.ashcon.app/mojang/v2/user/%s", skinName)); 14 | } 15 | 16 | @Override 17 | protected GameProfile readProfile(String skinName, JsonElement jsonElement) { 18 | JsonObject jsonObject = jsonElement.getAsJsonObject(); 19 | 20 | UUID uuid = UUID.fromString(jsonObject.get("uuid").getAsString()); 21 | String username = jsonObject.get("username").getAsString(); 22 | 23 | JsonObject textures = jsonObject.get("textures").getAsJsonObject(); 24 | JsonObject skin = textures.get("raw").getAsJsonObject(); 25 | 26 | return GameProfiles.newGameProfile(uuid, username, 27 | skin.get("value").getAsString(), skin.get("signature").getAsString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/GameProfiles.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.google.common.collect.Iterables; 4 | import com.mojang.authlib.GameProfile; 5 | import com.mojang.authlib.properties.Property; 6 | import java.util.Collection; 7 | import java.util.UUID; 8 | import javax.annotation.Nonnull; 9 | import javax.annotation.Nullable; 10 | 11 | public final class GameProfiles { 12 | static final String TEXTURES_PROPERTY_NAME = "textures"; 13 | 14 | @Nonnull 15 | public static GameProfile newGameProfile(UUID uuid, String name, String value, String signature) { 16 | GameProfile gameProfile = new GameProfile(uuid, name); 17 | 18 | Property property = new Property(TEXTURES_PROPERTY_NAME, value, signature); 19 | gameProfile.getProperties().put(TEXTURES_PROPERTY_NAME, property); 20 | 21 | return gameProfile; 22 | } 23 | 24 | @Nullable 25 | public static Property getTextureProperty(GameProfile gameProfile) { 26 | if (gameProfile.getProperties().containsKey(TEXTURES_PROPERTY_NAME)) { 27 | Collection textures = gameProfile.getProperties().get(TEXTURES_PROPERTY_NAME); 28 | return Iterables.getFirst(textures, null); 29 | } 30 | return null; 31 | } 32 | 33 | private GameProfiles() {} 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/MineSkinFetcher.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import static com.google.common.net.HttpHeaders.CONTENT_TYPE; 4 | import static java.nio.charset.StandardCharsets.UTF_8; 5 | 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonObject; 8 | import com.mojang.authlib.GameProfile; 9 | import java.net.URI; 10 | import java.net.URLEncoder; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.Builder; 13 | import java.util.UUID; 14 | 15 | public class MineSkinFetcher extends AbstractSkinFetcherServer { 16 | private static final String FORM_UTF_MIME = "application/x-www-form-urlencoded; charset=utf-8"; 17 | 18 | @Override 19 | protected URI getUriForRequest(String skinName) { 20 | return URI.create("https://api.mineskin.org/generate/url"); 21 | } 22 | 23 | @Override 24 | public Builder prepareRequest(String skinName) { 25 | return super.prepareRequest(skinName) 26 | .header(CONTENT_TYPE, FORM_UTF_MIME) 27 | .POST(HttpRequest.BodyPublishers.ofString("url=" + URLEncoder.encode(skinName, UTF_8))); 28 | } 29 | 30 | @Override 31 | protected GameProfile readProfile(String skinName, JsonElement jsonElement) { 32 | JsonObject jsonObject = jsonElement.getAsJsonObject(); 33 | JsonObject data = jsonObject.get("data").getAsJsonObject(); 34 | 35 | UUID uuid = UUID.fromString(data.get("uuid").getAsString()); 36 | JsonObject skin = data.get("texture").getAsJsonObject(); 37 | 38 | return GameProfiles.newGameProfile(uuid, skinName, 39 | skin.get("value").getAsString(), skin.get("signature").getAsString()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/SkinException.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | /** General exception for any {@link SkinFetcher} errors. */ 4 | public final class SkinException extends Exception { 5 | 6 | public SkinException(String message) { 7 | super(message); 8 | } 9 | 10 | public SkinException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/SkinFetcher.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.google.common.util.concurrent.ListenableFuture; 4 | import com.mojang.authlib.GameProfile; 5 | import javax.annotation.Nullable; 6 | 7 | public interface SkinFetcher { 8 | 9 | ListenableFuture fetchGameProfile( 10 | String name, @Nullable SkinFetcherListener listener); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/SkinFetcherImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import static com.google.common.util.concurrent.Futures.immediateFailedFuture; 4 | import static com.google.common.util.concurrent.Futures.immediateFuture; 5 | import static java.net.http.HttpClient.Redirect.ALWAYS; 6 | 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.common.util.concurrent.Futures; 9 | import com.google.common.util.concurrent.ListenableFuture; 10 | import com.google.common.util.concurrent.MoreExecutors; 11 | import com.mojang.authlib.GameProfile; 12 | import java.net.ProxySelector; 13 | import java.net.http.HttpClient; 14 | import java.net.http.HttpResponse; 15 | import java.net.http.HttpResponse.BodyHandlers; 16 | import java.time.Duration; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.concurrent.ExecutionException; 21 | import java.util.concurrent.Executor; 22 | import javax.annotation.Nullable; 23 | 24 | public class SkinFetcherImpl implements SkinFetcher { 25 | 26 | /** Receiver for output of {@link #fetchGameProfile}. */ 27 | public interface SkinGameProfileCollector { 28 | void acceptSkinGameProfile(String name, GameProfile profile); 29 | void acceptSkinError(String name, Throwable t); 30 | } 31 | 32 | /** Builder for {@link SkinFetcherImpl}. */ 33 | public static final class Builder { 34 | private Executor skinExecutor; 35 | private HttpClient httpClient; 36 | private final ImmutableList.Builder serverBuilder = ImmutableList.builder(); 37 | private Optional optionalSkinGameProfileCollector = Optional.empty(); 38 | 39 | public Builder addSkinFetcherServer(SkinFetcherServer... skinFetcherServers) { 40 | serverBuilder.addAll(ImmutableList.copyOf(skinFetcherServers)); 41 | return this; 42 | } 43 | 44 | public Builder setOptionalSkinGameProfileCollector( 45 | Optional optionalSkinGameProfileCollector) { 46 | this.optionalSkinGameProfileCollector = optionalSkinGameProfileCollector; 47 | return this; 48 | } 49 | 50 | public Builder setHttpClient(HttpClient httpClient) { 51 | this.httpClient = httpClient; 52 | return this; 53 | } 54 | 55 | public Builder withSyncExecutor() { 56 | this.skinExecutor = MoreExecutors.directExecutor(); 57 | return this; 58 | } 59 | 60 | public Builder setSkinExecutor(Executor skinExecutor) { 61 | this.skinExecutor = skinExecutor; 62 | return this; 63 | } 64 | 65 | public SkinFetcherImpl build() { 66 | if (httpClient == null) { 67 | httpClient = HttpClient.newBuilder() 68 | .connectTimeout(Duration.ofSeconds(10)) 69 | .followRedirects(ALWAYS) 70 | .proxy(ProxySelector.getDefault()) 71 | .build(); 72 | } 73 | return new SkinFetcherImpl(this); 74 | } 75 | } 76 | 77 | /** Returns a Builder for {@link SkinFetcherImpl}. */ 78 | public static Builder builder() { 79 | return new Builder(); 80 | } 81 | 82 | private final HttpClient httpClient; 83 | private final Executor executor; 84 | private final ImmutableList skinFetcherServers; 85 | private Optional optionalSkinGameProfileCollector; 86 | 87 | private SkinFetcherImpl(Builder builder) { 88 | this.httpClient = builder.httpClient; 89 | this.executor = builder.skinExecutor; 90 | this.skinFetcherServers = builder.serverBuilder.build(); 91 | this.optionalSkinGameProfileCollector = builder.optionalSkinGameProfileCollector; 92 | } 93 | 94 | @Override 95 | public ListenableFuture fetchGameProfile( 96 | String name, @Nullable SkinFetcherListener listener) { 97 | List> allProfiles = getAllProfiles(name); 98 | ListenableFuture fetchGameProfileFuture = 99 | Futures.transformAsync( 100 | Futures.whenAllComplete(allProfiles) 101 | .callAsync(() -> { 102 | List retrievedGameProfiles = new ArrayList<>(); 103 | for (ListenableFuture allProfile : allProfiles) { 104 | try { 105 | GameProfile gameProfile = Futures.getDone(allProfile); 106 | if (gameProfile == null) { 107 | continue; 108 | } 109 | retrievedGameProfiles.add(gameProfile); 110 | optionalSkinGameProfileCollector.ifPresent( 111 | collector -> collector.acceptSkinGameProfile(name, gameProfile)); 112 | } catch (ExecutionException e) { 113 | if (e.getCause() instanceof SkinException) { 114 | if (allProfiles.size() == 1) { 115 | return immediateFailedFuture(e); 116 | } 117 | } 118 | optionalSkinGameProfileCollector.ifPresent( 119 | collector -> collector.acceptSkinError(name, e)); 120 | } 121 | } 122 | return immediateFuture(retrievedGameProfiles); 123 | }, executor), 124 | gameProfiles -> { 125 | if (!gameProfiles.isEmpty()) { 126 | GameProfile gameProfile = gameProfiles.get(0); 127 | if (listener != null) { 128 | listener.onComplete(gameProfile); 129 | } 130 | return Futures.immediateFuture(gameProfile); 131 | } 132 | return Futures.immediateFailedFuture(new SkinException("No skin found for: " + name)); 133 | }, executor); 134 | return Futures.catchingAsync( 135 | fetchGameProfileFuture, 136 | Exception.class, 137 | exception -> { 138 | if (listener != null) { 139 | listener.onError(exception); 140 | } 141 | return immediateFailedFuture(exception); 142 | }, executor); 143 | } 144 | 145 | private List> getAllProfiles(String name) { 146 | List> fetchedGameProfilesFuture = new ArrayList<>(); 147 | for (SkinFetcherServer skinServer : skinFetcherServers) { 148 | try { 149 | HttpResponse httpResponse = httpClient.send( 150 | skinServer.prepareRequest(name).build(), BodyHandlers.ofString()); 151 | fetchedGameProfilesFuture.add( 152 | immediateFuture(skinServer.readProfile(name, httpResponse))); 153 | } catch (Exception e) { 154 | ListenableFuture errorFuture = 155 | immediateFailedFuture(new SkinException(name, e)); 156 | fetchedGameProfilesFuture.add(errorFuture); 157 | } 158 | } 159 | return fetchedGameProfilesFuture; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/SkinFetcherListener.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | 5 | public interface SkinFetcherListener { 6 | 7 | default void onComplete(GameProfile gameProfile) {} 8 | 9 | default void onError(Throwable error) {} 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/gonalez/znpcs/skin/SkinFetcherServer.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import java.net.URI; 5 | import java.net.http.HttpRequest; 6 | import java.net.http.HttpResponse; 7 | import java.time.Duration; 8 | 9 | public abstract class SkinFetcherServer { 10 | private static final Duration SKIN_REQUEST_DEFAULT_TIMEOUT = Duration.ofSeconds(5); 11 | 12 | protected abstract URI getUriForRequest(String skinName); 13 | 14 | public abstract GameProfile readProfile( 15 | String skinName, HttpResponse httpResponse); 16 | 17 | public HttpRequest.Builder prepareRequest(String skinName) { 18 | return HttpRequest.newBuilder() 19 | .uri(getUriForRequest(skinName)) 20 | .timeout(SKIN_REQUEST_DEFAULT_TIMEOUT); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/test/java/io/github/gonalez/znpcs/commands/CommandTest.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.commands; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | import io.github.gonalez.znpcs.command.Command; 7 | import io.github.gonalez.znpcs.command.CommandProvider; 8 | import io.github.gonalez.znpcs.command.CommandResult; 9 | import java.util.Collection; 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** Unit tests for {@link Command}. */ 13 | public class CommandTest { 14 | 15 | public static class ExampleCommand extends Command { 16 | private final String name; 17 | 18 | public ExampleCommand(String name) { 19 | this.name = name; 20 | } 21 | 22 | @Override 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | @Override 28 | protected CommandResult execute(CommandProvider commandProvider, ImmutableList args) { 29 | return newCommandResult(); 30 | } 31 | 32 | @Override 33 | protected int getMandatoryArguments() { 34 | return 1; 35 | } 36 | 37 | @Override 38 | protected Collection getChildren() { 39 | return ImmutableList.of(); 40 | } 41 | } 42 | 43 | @Test 44 | public void testExecuteCommand_commandResult() throws Exception { 45 | ExampleCommand exampleCommand = new ExampleCommand("hello"); 46 | CommandResult commandResult = exampleCommand.executeCommand(CommandProvider.NOOP, ImmutableList.of()); 47 | assertThat(commandResult.getActualCommand()).isEqualTo(exampleCommand); 48 | assertThat(commandResult.getActualCommand().getName()).isEqualTo("hello"); 49 | } 50 | 51 | public static final class ExampleTreeCommand extends Command { 52 | 53 | @Override 54 | public String getName() { 55 | return ""; 56 | } 57 | 58 | @Override 59 | public CommandResult execute(CommandProvider commandProvider, ImmutableList args) { 60 | return newCommandResult(); 61 | } 62 | 63 | @Override 64 | protected int getMandatoryArguments() { 65 | return 1; 66 | } 67 | 68 | @Override 69 | protected Collection getChildren() { 70 | return ImmutableList.of(new ExampleCommand("foo") { 71 | @Override 72 | protected Collection getChildren() { 73 | return ImmutableList.of(new ExampleCommand("bar")); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | @Test 80 | public void testExecuteCommand_withChildren_returnsCorrectCommandInstance() throws Exception { 81 | Command treeCommand = new ExampleTreeCommand(); 82 | CommandResult commandResult = treeCommand.executeCommand(CommandProvider.NOOP, ImmutableList.of("foo", "bar")); 83 | assertThat(commandResult.getActualCommand()).isInstanceOf(CommandTest.ExampleCommand.class); 84 | assertThat(commandResult.getActualCommand().getName()).isEqualTo("bar"); 85 | commandResult = treeCommand.executeCommand(CommandProvider.NOOP, ImmutableList.of("bar", "foo")); 86 | assertThat(commandResult.getActualCommand()).isNotInstanceOf(ExampleCommand.class); 87 | } 88 | } -------------------------------------------------------------------------------- /core/src/test/java/io/github/gonalez/znpcs/configuration/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - Gaston Gonzalez (Gonalez). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.gonalez.znpcs.configuration; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static java.nio.charset.StandardCharsets.UTF_8; 21 | 22 | import com.google.common.collect.Lists; 23 | import com.google.common.io.Files; 24 | import com.google.gson.GsonBuilder; 25 | import java.nio.file.Path; 26 | import java.util.List; 27 | import org.junit.jupiter.api.io.TempDir; 28 | import org.junit.runner.RunWith; 29 | import org.junit.runners.JUnit4; 30 | import org.yaml.snakeyaml.DumperOptions; 31 | import org.yaml.snakeyaml.DumperOptions.FlowStyle; 32 | import org.yaml.snakeyaml.Yaml; 33 | import org.junit.jupiter.api.Test; 34 | 35 | /** Unit tests for {@link Configuration}. */ 36 | @RunWith(JUnit4.class) 37 | public class ConfigurationTest { 38 | @TempDir public Path tempFolder; 39 | 40 | public static final class ExampleConfig extends Configuration { 41 | 42 | @ConfigurationKey(name = "view_distance") 43 | public int viewDistance; 44 | 45 | @ConfigurationKey(name = "names") 46 | public List names; 47 | } 48 | 49 | @Test 50 | public void testConfigurationManager_withGson_writeExampleConfig() throws Exception { 51 | Path testPath = tempFolder.resolve("config.json"); 52 | 53 | GsonConfigurationIndex configurationManager = 54 | new GsonConfigurationIndex(new GsonBuilder().setPrettyPrinting().create()) { 55 | @Override 56 | public Path getConfigFilePath(Class configClass) { 57 | return testPath; 58 | } 59 | }; 60 | 61 | ExampleConfig exampleConfig = new ExampleConfig(); 62 | exampleConfig.viewDistance = 32; 63 | 64 | configurationManager.writeConfiguration(exampleConfig); 65 | assertThat(Files.asCharSource(testPath.toFile(), UTF_8).read()) 66 | .isEqualTo( 67 | "{\n" 68 | + " \"view_distance\": 32\n" 69 | + "}"); 70 | 71 | 72 | exampleConfig = configurationManager.createConfiguration(ExampleConfig.class); 73 | assertThat(exampleConfig.viewDistance).isEqualTo(32); 74 | assertThat(exampleConfig.names).isNull(); 75 | } 76 | 77 | @Test 78 | public void testConfigurationManager_withYaml_writeExampleConfig() throws Exception { 79 | Path testPath = tempFolder.resolve("config.yml"); 80 | 81 | DumperOptions options = new DumperOptions(); 82 | options.setDefaultFlowStyle(FlowStyle.BLOCK); 83 | options.setIndent(2); 84 | 85 | YamlConfigurationIndex configurationManager = 86 | new YamlConfigurationIndex(new Yaml(options)) { 87 | @Override 88 | public Path getConfigFilePath(Class configClass) { 89 | return testPath; 90 | }; 91 | }; 92 | 93 | ExampleConfig exampleConfig = new ExampleConfig(); 94 | exampleConfig.viewDistance = 32; 95 | List contents = Lists.newArrayList("hey", "world"); 96 | exampleConfig.names = contents; 97 | 98 | configurationManager.writeConfiguration(exampleConfig); 99 | assertThat(Files.asCharSource(testPath.toFile(), UTF_8).read()) 100 | .isEqualTo( 101 | "view_distance: 32\n" 102 | + "names:\n" 103 | + "- hey\n" 104 | + "- world\n"); 105 | 106 | exampleConfig = configurationManager.createConfiguration(ExampleConfig.class); 107 | assertThat(exampleConfig.viewDistance).isEqualTo(32); 108 | assertThat(exampleConfig.names).isEqualTo(contents); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /core/src/test/java/io/github/gonalez/znpcs/skin/SkinFetcherTest.java: -------------------------------------------------------------------------------- 1 | package io.github.gonalez.znpcs.skin; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | import static org.junit.Assert.assertThrows; 5 | 6 | import com.google.common.util.concurrent.ListenableFuture; 7 | import com.mojang.authlib.GameProfile; 8 | import com.mojang.authlib.properties.Property; 9 | import java.util.UUID; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.Future; 13 | import org.junit.Test; 14 | import org.junit.jupiter.api.BeforeAll; 15 | import org.junitpioneer.jupiter.RetryingTest; 16 | 17 | /** Unit tests for {@link SkinFetcher}. */ 18 | public class SkinFetcherTest { 19 | private static SkinFetcher skinFetcher; 20 | 21 | @BeforeAll 22 | static void setup() { 23 | skinFetcher = SkinFetcherImpl.builder() 24 | .setSkinExecutor(Executors.newSingleThreadExecutor()) 25 | .addSkinFetcherServer(new AshconSkinFetcherServer(), new MineSkinFetcher()) 26 | .build(); 27 | } 28 | 29 | @Test 30 | public void testSkinFetcher_name() throws Exception { 31 | Future fetched = skinFetcher.fetchGameProfile("Qentin", null); 32 | 33 | GameProfile gameProfile = fetched.get(); 34 | Property property = GameProfiles.getTextureProperty(gameProfile); 35 | assertThat(property).isNotNull(); 36 | 37 | assertThat(gameProfile.getId()).isEqualTo(UUID.fromString("51800622-9dae-4b23-84a7-26d6a27c60db")); 38 | assertThat(property.getValue()).isEqualTo("ewogICJ0aW1lc3RhbXAiIDogMTcwNTg3MjE5MDg4OCwKICAicHJvZmlsZUlkIiA6ICI1MTgwMDYyMjlkYWU0YjIzODRhNzI2ZDZhMjdjNjBkYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJRZW50aW4iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGQzNjNiMTBkODBhMjNmOTE3MGU5YWUyMGIxZjE1NTU2YzYyZWNmNGUxMDdkMjU5YWNkODJhNWExODdlMjJlNCIKICAgIH0KICB9Cn0="); 39 | assertThat(property.getSignature()).isEqualTo("WOQe4LOqACX9y3KXeZATfXUP81VsYqnARGSB+P1BWhBmV3Ty1D3JbdHuPauPvJB0uCCSUBgCgjryzRl23PxwnsPoF6cCLQp0uqGjyrBVJ94TzR86fzXSdVHtw10N/Fpbz6PRAMZ/roA+rmcjY2aVXWUtr4w2dWGEeLzRFtmnUSR1sPaXom3XlG+51VU51yRBc+0DbPb/WnD297qYlzUYxmSUl6F93EQHvkbD9fbUdPXErsT7DwSQYvjDm9fy4vT98LRmZiSTyefN/OUMERRcI/EDWHwHiix0jhwb3j4JVBueVdsI7Wt+5Lp3GQmAkEeuS0WA+ovOuWiFgtSZ6WO0LkmIPzjtef49G8hrn2GEj3JWHGTysTiowqtGb7S7Q4BeQy9dYFBECKQ4tWoGsKjHLFve2bVXz1S5V7MptG83vhTSYmvwZQ0/rbOMfKGVSrPqOK4fClASkAkT35mVW8AcXSC1jD38+GLwPaJ948Ju8PW8fcgxvya24y/D876xNo4FD3ny3SG4uEqIGeM/wrSrPImDhXmMRBxdqmhvoD3qg/2+UVNcjo2WrzNjJ+hXaozTs9tlpgvIkfHqN3s5c0Pvlk9IcNAMe6sQxeKz20Cl6CG0/0bxSGr4SEkZ2oSAq6aLMUSH3ymq9TKVYKcXIB12WpTyO9Wa8zt3Hoq9oLoPO3Q="); 40 | } 41 | 42 | @RetryingTest(maxAttempts = 3, suspendForMs = 1000) 43 | public void testSkinFetcher_image() throws Exception { 44 | Future fetched = skinFetcher.fetchGameProfile("https://i.imgur.com/pVeIlTz.png", null); 45 | 46 | GameProfile gameProfile = fetched.get(); 47 | Property property = GameProfiles.getTextureProperty(gameProfile); 48 | assertThat(property).isNotNull(); 49 | 50 | assertThat(property.getSignature()).isEqualTo("Ix3UhKVhw9tSnhtGeZZn+Sr1cySHz53bZw/oGIW5z+FdOWo7Q2swRO1o/zDXsbRV6oHETQm3sWhwQWbnuaDPQfMZIwpsO91xCXJ4dYwQrwxNzd3/cX8Tu8IQGvVCm0UyXq4mL2qGIJKYLgEbi8C29AWjee8zKQ2K0TOaiTCQBmy/mfpQHLrk4abDo/Jy+DS3CYm8ENLmIvPy0ERd967vv7Yz7KONjUPaacGQx5oY2q4AS4Plg9wDGdwmAL6CNC+iMLM0ajdjztxAhSZboM5bnd3BzPdKArIQmLuI95b4Qko45uXFS/kEDh3uey3QL2IBxqgpdDXPKgLkAKW3meqeQPq6UNhT3zoBxUPf9uhPHxVpfbWfizqHHFgANaz23QqIUsdhqkTbwgBkpDYzCJ7HEwIyNEpkMAT3AZ/xc4ny03QcnSvPLgD329UNZQvmAHwuNihX/3XrZaFx6467n9B66YAPfuWGi6P8lFoaehxDIWqxicANTZtVXv5JBdIgMpV+uwinO04amKC3nuE8chFig9WMFcitjpuJ9ZGRAf+LVSLiUQWo7Gx+EOw0T2nrac26+Vslxg50qWHOzYIIJD88X0iWibufPg4M4v23Y6jUOZ0QYVEJJA7OoQ8h+bPtT88on8UELdckh/4ltiRE3myYAhfodvfgbgdIxFJlxGJe4qU="); 51 | assertThat(property.getValue()).isEqualTo("ewogICJ0aW1lc3RhbXAiIDogMTYyODY2ODM4MTc0MiwKICAicHJvZmlsZUlkIiA6ICI3MmNiMDYyMWU1MTA0MDdjOWRlMDA1OTRmNjAxNTIyZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNb3M5OTAiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzQyZjQ3YzBiNWQyZGQ3NDc1ZDNlNzg3NzVhMzE5NGE3ZTU2YWQxNmE3ZTVlNmM0YTI0YjJiOWVmODY3YjViNyIKICAgIH0KICB9Cn0="); 52 | } 53 | 54 | @Test 55 | public void testSkinFetcher_fail() throws Exception { 56 | ListenableFuture fetched = skinFetcher.fetchGameProfile("https://textures.minecraft.net", null); 57 | 58 | ExecutionException e = assertThrows(ExecutionException.class, fetched::get); 59 | assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("Skin not found"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP_ID=io.github.gonalez.znpcs 2 | VERSION_NAME=4.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonalez/znpcs/73b2374c5d489fefcb132a3fc610c0e9dd4fd0f8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'znpcs' 2 | include 'bukkit' 3 | include 'core' --------------------------------------------------------------------------------