Compare commits
	
		
			29 commits
		
	
	
		
			aec2854254
			...
			bf0b58b4b6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bf0b58b4b6 | |||
| 
							 | 
						8b1fde3652 | ||
| 
							 | 
						7bffd2f79d | ||
| 
							 | 
						9304c96723 | ||
| 
							 | 
						a3b9bda53a | ||
| 
							 | 
						79761cba92 | ||
| 
							 | 
						7495493c27 | ||
| 
							 | 
						d02d0cb5e9 | ||
| 
							 | 
						71f52a987b | ||
| 
							 | 
						5c99681561 | ||
| 
							 | 
						79ede2b586 | ||
| 
							 | 
						304278edd8 | ||
| 
							 | 
						0272a9ef68 | ||
| 
							 | 
						d7c0870546 | ||
| 
							 | 
						0f7ad57b64 | ||
| 
							 | 
						aeeba809be | ||
| 
							 | 
						74463178fe | ||
| 
							 | 
						1b3a197085 | ||
| 
							 | 
						cf8ce5097f | ||
| 
							 | 
						13a47fa743 | ||
| 
							 | 
						36a939e497 | ||
| 
							 | 
						979b95eed5 | ||
| 
							 | 
						18c5cc0e24 | ||
| 
							 | 
						e7a82d55e7 | ||
| 
							 | 
						06889a221e | ||
| 
							 | 
						aafdf0b3f9 | ||
| 
							 | 
						71430afcc3 | ||
| 
							 | 
						3248418464 | ||
| 
							 | 
						18cdef4527 | 
					 38 changed files with 494 additions and 61 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								.github/znpc.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/znpc.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 54 KiB  | 
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
that players can interact with to perform actions like switching servers on a network or executing commands.
 | 
					that players can interact with to perform actions like switching servers on a network or executing commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This plugin is a remake of a plugin called ZNPCs, we originally started because the maintainer of ZNPCs decided to announce that he was 
 | 
					This plugin is a remake of a plugin called ZNPCs, we originally started because the maintainer of ZNPCs decided to announce that he was 
 | 
				
			||||||
[dropping support for the plugin](https://media.discordapp.net/attachments/1093914615873806477/1098409384855474237/znpc.png).
 | 
					[dropping support for the plugin](https://github.com/Pyrbu/ZNPCsPlus/blob/2.X/.github/znpc.png?raw=true).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.pyr.lol/job/ZNPCsPlus/)
 | 
					Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.pyr.lol/job/ZNPCsPlus/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.
 | 
				
			||||||
### Requirements, Extensions & Supported Software
 | 
					### Requirements, Extensions & Supported Software
 | 
				
			||||||
Requirements:
 | 
					Requirements:
 | 
				
			||||||
- Java 8+
 | 
					- Java 8+
 | 
				
			||||||
- Minecraft 1.8 - 1.20.4
 | 
					- Minecraft 1.8 - 1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Supported Softwares:
 | 
					Supported Softwares:
 | 
				
			||||||
- Spigot ([Website](https://www.spigotmc.org/))
 | 
					- Spigot ([Website](https://www.spigotmc.org/))
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,6 @@ Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/i
 | 
				
			||||||
- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation
 | 
					- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation
 | 
				
			||||||
- [gson](https://github.com/google/gson) - JSON parsing library made by Google
 | 
					- [gson](https://github.com/google/gson) - JSON parsing library made by Google
 | 
				
			||||||
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
 | 
					- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
 | 
				
			||||||
- [SpigotResourcesAPI](https://github.com/robertlit/SpigotResourcesAPI/) - Spigot API wrapper used for updater
 | 
					 | 
				
			||||||
- [adventure](https://docs.advntr.dev/) - Minecraft text api
 | 
					- [adventure](https://docs.advntr.dev/) - Minecraft text api
 | 
				
			||||||
- [DazzleConf](https://github.com/A248/DazzleConf) - Configuration library
 | 
					- [DazzleConf](https://github.com/A248/DazzleConf) - Configuration library
 | 
				
			||||||
- [Director](https://github.com/Pyrbu/Director) - Command library
 | 
					- [Director](https://github.com/Pyrbu/Director) - Command library
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					package lol.pyr.znpcsplus.api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
 | 
				
			||||||
 | 
					import org.bukkit.Bukkit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Provider for the registered entity property registry instance
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class NpcPropertyRegistryProvider {
 | 
				
			||||||
 | 
					    private static EntityPropertyRegistry registry = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private NpcPropertyRegistryProvider() {
 | 
				
			||||||
 | 
					        throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Static method that returns the entity property registry instance of the plugin
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return The instance of the entity property registry for the ZNPCsPlus plugin
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static EntityPropertyRegistry get() {
 | 
				
			||||||
 | 
					        if (registry == null) throw new IllegalStateException(
 | 
				
			||||||
 | 
					                "ZNPCsPlus plugin isn't loaded yet!\n" +
 | 
				
			||||||
 | 
					                        "Please add it to your plugin.yml as a depend or softdepend."
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return registry;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Internal method used to register the main instance of the plugin as the entity property registry provider
 | 
				
			||||||
 | 
					     * You probably shouldn't call this method under any circumstances
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param api Instance of the ZNPCsPlus entity property registry
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void register(EntityPropertyRegistry api) {
 | 
				
			||||||
 | 
					        NpcPropertyRegistryProvider.registry = api;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Internal method used to unregister the plugin from the provider when the plugin shuts down
 | 
				
			||||||
 | 
					     * You probably shouldn't call this method under any circumstances
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static void unregister() {
 | 
				
			||||||
 | 
					        Bukkit.getServicesManager().unregister(registry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,18 @@ public interface Npc extends PropertyHolder {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void setLocation(NpcLocation location);
 | 
					    void setLocation(NpcLocation location);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the world of this NPC
 | 
				
			||||||
 | 
					     * @param world The bukkit world to set
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void setWorld(World world);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the world of this NPC
 | 
				
			||||||
 | 
					     * @param name The name world to set
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void setWorld(String name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Gets the hologram of this NPC
 | 
					     * Gets the hologram of this NPC
 | 
				
			||||||
     * @return The {@link Hologram} of this NPC
 | 
					     * @return The {@link Hologram} of this NPC
 | 
				
			||||||
| 
						 | 
					@ -69,6 +81,13 @@ public interface Npc extends PropertyHolder {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    World getWorld();
 | 
					    World getWorld();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the name of the world this NPC is in
 | 
				
			||||||
 | 
					     * Unlike {@link Npc#getWorld()} this will never be null
 | 
				
			||||||
 | 
					     * @return The name of the world this NPC is in
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String getWorldName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Gets the list of actions for this NPC
 | 
					     * Gets the list of actions for this NPC
 | 
				
			||||||
     * @return The {@link List} of {@link InteractionAction}s for this NPC
 | 
					     * @return The {@link List} of {@link InteractionAction}s for this NPC
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,5 +20,8 @@ public enum NpcPose {
 | 
				
			||||||
    ROARING,
 | 
					    ROARING,
 | 
				
			||||||
    SNIFFING,
 | 
					    SNIFFING,
 | 
				
			||||||
    EMERGING,
 | 
					    EMERGING,
 | 
				
			||||||
    DIGGING
 | 
					    DIGGING,
 | 
				
			||||||
 | 
					    SLIDING,
 | 
				
			||||||
 | 
					    SHOOTING,
 | 
				
			||||||
 | 
					    INHALING,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,23 +4,15 @@ plugins {
 | 
				
			||||||
    id "xyz.jpenilla.run-paper" version "2.2.0"
 | 
					    id "xyz.jpenilla.run-paper" version "2.2.0"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
processResources {
 | 
					runServer {
 | 
				
			||||||
    expand("version": version)
 | 
					    javaLauncher = javaToolchains.launcherFor {
 | 
				
			||||||
 | 
					        languageVersion = JavaLanguageVersion.of(21)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    minecraftVersion "1.20.6"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
publishing {
 | 
					processResources {
 | 
				
			||||||
    publications {
 | 
					    expand("version": version)
 | 
				
			||||||
        mavenJava(MavenPublication) {
 | 
					 | 
				
			||||||
            from components.java
 | 
					 | 
				
			||||||
            artifactId = "znpcsplus-plugin"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            pom {
 | 
					 | 
				
			||||||
                name.set("znpcsplus-plugin")
 | 
					 | 
				
			||||||
                description.set("The ZNPCsPlus plugin")
 | 
					 | 
				
			||||||
                url.set("https://github.com/Pyrbu/ZNPCsPlus")
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
| 
						 | 
					@ -28,12 +20,11 @@ dependencies {
 | 
				
			||||||
    compileOnly "com.github.retrooper:packetevents-spigot:2.6.0" // Packets
 | 
					    compileOnly "com.github.retrooper:packetevents-spigot:2.6.0" // Packets
 | 
				
			||||||
    implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
 | 
					    implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
 | 
				
			||||||
    implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
 | 
					    implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
 | 
				
			||||||
    implementation "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker
 | 
					 | 
				
			||||||
    implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
 | 
					    implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
 | 
				
			||||||
    implementation "lol.pyr:director-adventure:2.1.2" // Commands
 | 
					    implementation "lol.pyr:director-adventure:2.1.2" // Commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fancy text library
 | 
					    // Fancy text library
 | 
				
			||||||
    implementation "net.kyori:adventure-platform-bukkit:4.3.3"
 | 
					    implementation "net.kyori:adventure-platform-bukkit:4.3.4"
 | 
				
			||||||
    implementation "net.kyori:adventure-text-minimessage:4.17.0"
 | 
					    implementation "net.kyori:adventure-text-minimessage:4.17.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation project(":api")
 | 
					    implementation project(":api")
 | 
				
			||||||
| 
						 | 
					@ -52,6 +43,7 @@ shadowJar {
 | 
				
			||||||
    relocate "net.kyori", "lol.pyr.znpcsplus.libraries.kyori"
 | 
					    relocate "net.kyori", "lol.pyr.znpcsplus.libraries.kyori"
 | 
				
			||||||
    relocate "org.checkerframework", "lol.pyr.znpcsplus.libraries.checkerframework"
 | 
					    relocate "org.checkerframework", "lol.pyr.znpcsplus.libraries.checkerframework"
 | 
				
			||||||
    relocate "com.google", "lol.pyr.znpcsplus.libraries.google"
 | 
					    relocate "com.google", "lol.pyr.znpcsplus.libraries.google"
 | 
				
			||||||
 | 
					    relocate "com.google.gson", "lol.pyr.znpcsplus.libraries.gson"
 | 
				
			||||||
    relocate "org.yaml.snakeyaml", "lol.pyr.znpcsplus.libraries.snakeyaml"
 | 
					    relocate "org.yaml.snakeyaml", "lol.pyr.znpcsplus.libraries.snakeyaml"
 | 
				
			||||||
    relocate "space.arim.dazzleconf", "lol.pyr.znpcsplus.libraries.dazzleconf"
 | 
					    relocate "space.arim.dazzleconf", "lol.pyr.znpcsplus.libraries.dazzleconf"
 | 
				
			||||||
    relocate "lol.pyr.director", "lol.pyr.znpcsplus.libraries.command"
 | 
					    relocate "lol.pyr.director", "lol.pyr.znpcsplus.libraries.command"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ import lol.pyr.director.adventure.parse.primitive.FloatParser;
 | 
				
			||||||
import lol.pyr.director.adventure.parse.primitive.IntegerParser;
 | 
					import lol.pyr.director.adventure.parse.primitive.IntegerParser;
 | 
				
			||||||
import lol.pyr.director.common.message.Message;
 | 
					import lol.pyr.director.common.message.Message;
 | 
				
			||||||
import lol.pyr.znpcsplus.api.NpcApiProvider;
 | 
					import lol.pyr.znpcsplus.api.NpcApiProvider;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.api.NpcPropertyRegistryProvider;
 | 
				
			||||||
import lol.pyr.znpcsplus.api.interaction.InteractionType;
 | 
					import lol.pyr.znpcsplus.api.interaction.InteractionType;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.*;
 | 
					import lol.pyr.znpcsplus.commands.*;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.action.*;
 | 
					import lol.pyr.znpcsplus.commands.action.*;
 | 
				
			||||||
| 
						 | 
					@ -22,6 +23,7 @@ import lol.pyr.znpcsplus.commands.property.PropertyRemoveCommand;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.property.PropertySetCommand;
 | 
					import lol.pyr.znpcsplus.commands.property.PropertySetCommand;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.storage.ImportCommand;
 | 
					import lol.pyr.znpcsplus.commands.storage.ImportCommand;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.storage.LoadAllCommand;
 | 
					import lol.pyr.znpcsplus.commands.storage.LoadAllCommand;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.commands.storage.MigrateCommand;
 | 
				
			||||||
import lol.pyr.znpcsplus.commands.storage.SaveAllCommand;
 | 
					import lol.pyr.znpcsplus.commands.storage.SaveAllCommand;
 | 
				
			||||||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
					import lol.pyr.znpcsplus.config.ConfigManager;
 | 
				
			||||||
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
 | 
					import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
 | 
				
			||||||
| 
						 | 
					@ -38,6 +40,7 @@ import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
 | 
				
			||||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
					import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
				
			||||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
					import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
				
			||||||
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
 | 
					import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.storage.NpcStorageType;
 | 
				
			||||||
import lol.pyr.znpcsplus.tasks.HologramRefreshTask;
 | 
					import lol.pyr.znpcsplus.tasks.HologramRefreshTask;
 | 
				
			||||||
import lol.pyr.znpcsplus.tasks.NpcProcessorTask;
 | 
					import lol.pyr.znpcsplus.tasks.NpcProcessorTask;
 | 
				
			||||||
import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
 | 
					import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
 | 
				
			||||||
| 
						 | 
					@ -78,12 +81,23 @@ public class ZNpcsPlus {
 | 
				
			||||||
    private final PacketEventsAPI<Plugin> packetEvents;
 | 
					    private final PacketEventsAPI<Plugin> packetEvents;
 | 
				
			||||||
    private final ZNpcsPlusBootstrap bootstrap;
 | 
					    private final ZNpcsPlusBootstrap bootstrap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final ConfigManager configManager;
 | 
				
			||||||
 | 
					    private final MojangSkinCache skinCache;
 | 
				
			||||||
 | 
					    private final EntityPropertyRegistryImpl propertyRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ZNpcsPlus(ZNpcsPlusBootstrap bootstrap) {
 | 
					    public ZNpcsPlus(ZNpcsPlusBootstrap bootstrap) {
 | 
				
			||||||
        this.bootstrap = bootstrap;
 | 
					        this.bootstrap = bootstrap;
 | 
				
			||||||
        packetEvents = SpigotPacketEventsBuilder.build(bootstrap);
 | 
					        packetEvents = SpigotPacketEventsBuilder.build(bootstrap);
 | 
				
			||||||
        PacketEvents.setAPI(packetEvents);
 | 
					        PacketEvents.setAPI(packetEvents);
 | 
				
			||||||
        packetEvents.getSettings().checkForUpdates(false);
 | 
					        packetEvents.getSettings().checkForUpdates(false);
 | 
				
			||||||
        packetEvents.load();
 | 
					        packetEvents.load();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        configManager = new ConfigManager(getDataFolder());
 | 
				
			||||||
 | 
					        skinCache = new MojangSkinCache(configManager);
 | 
				
			||||||
 | 
					        propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NpcPropertyRegistryProvider.register(propertyRegistry);
 | 
				
			||||||
 | 
					        shutdownTasks.add(NpcPropertyRegistryProvider::unregister);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void log(String str) {
 | 
					    private void log(String str) {
 | 
				
			||||||
| 
						 | 
					@ -113,11 +127,9 @@ public class ZNpcsPlus {
 | 
				
			||||||
        TaskScheduler scheduler = FoliaUtil.isFolia() ? new FoliaScheduler(bootstrap) : new SpigotScheduler(bootstrap);
 | 
					        TaskScheduler scheduler = FoliaUtil.isFolia() ? new FoliaScheduler(bootstrap) : new SpigotScheduler(bootstrap);
 | 
				
			||||||
        shutdownTasks.add(scheduler::cancelAll);
 | 
					        shutdownTasks.add(scheduler::cancelAll);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ConfigManager configManager = new ConfigManager(getDataFolder());
 | 
					
 | 
				
			||||||
        MojangSkinCache skinCache = new MojangSkinCache(configManager);
 | 
					 | 
				
			||||||
        EntityPropertyRegistryImpl propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
 | 
					 | 
				
			||||||
        PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
 | 
					        PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
 | 
				
			||||||
        propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer);
 | 
					        propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer, scheduler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
 | 
					        BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
 | 
				
			||||||
        ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
 | 
					        ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
 | 
				
			||||||
| 
						 | 
					@ -147,7 +159,7 @@ public class ZNpcsPlus {
 | 
				
			||||||
        pluginManager.registerEvents(new UserListener(userManager), bootstrap);
 | 
					        pluginManager.registerEvents(new UserListener(userManager), bootstrap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
 | 
					        registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
 | 
				
			||||||
                typeRegistry, propertyRegistry, importerRegistry, configManager);
 | 
					                typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log(ChatColor.WHITE + " * Starting tasks...");
 | 
					        log(ChatColor.WHITE + " * Starting tasks...");
 | 
				
			||||||
        if (configManager.getConfig().checkForUpdates()) {
 | 
					        if (configManager.getConfig().checkForUpdates()) {
 | 
				
			||||||
| 
						 | 
					@ -219,6 +231,7 @@ public class ZNpcsPlus {
 | 
				
			||||||
        versions.put(ServerVersion.V_1_17, LazyLoader.of(() -> new V1_17PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
					        versions.put(ServerVersion.V_1_17, LazyLoader.of(() -> new V1_17PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
				
			||||||
        versions.put(ServerVersion.V_1_19_3, LazyLoader.of(() -> new V1_19_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
					        versions.put(ServerVersion.V_1_19_3, LazyLoader.of(() -> new V1_19_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
				
			||||||
        versions.put(ServerVersion.V_1_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
					        versions.put(ServerVersion.V_1_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
				
			||||||
 | 
					        versions.put(ServerVersion.V_1_21_3, LazyLoader.of(() -> new V1_21_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ServerVersion version = packetEvents.getServerManager().getVersion();
 | 
					        ServerVersion version = packetEvents.getServerManager().getVersion();
 | 
				
			||||||
        if (versions.containsKey(version)) return versions.get(version).get();
 | 
					        if (versions.containsKey(version)) return versions.get(version).get();
 | 
				
			||||||
| 
						 | 
					@ -233,7 +246,7 @@ public class ZNpcsPlus {
 | 
				
			||||||
    private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
 | 
					    private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
 | 
				
			||||||
                                  ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
 | 
					                                  ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
 | 
				
			||||||
                                  EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
 | 
					                                  EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
 | 
				
			||||||
                                  ConfigManager configManager) {
 | 
					                                  ConfigManager configManager, PacketFactory packetFactory) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
 | 
					        Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
 | 
				
			||||||
        CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
 | 
					        CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
 | 
				
			||||||
| 
						 | 
					@ -282,6 +295,7 @@ public class ZNpcsPlus {
 | 
				
			||||||
        registerEnumParser(manager, Sound.class, incorrectUsageMessage);
 | 
					        registerEnumParser(manager, Sound.class, incorrectUsageMessage);
 | 
				
			||||||
        registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
 | 
					        registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
 | 
				
			||||||
        registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
 | 
					        registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
 | 
				
			||||||
 | 
					        registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
 | 
					        manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
 | 
				
			||||||
                .addSubcommand("center", new CenterCommand(npcRegistry))
 | 
					                .addSubcommand("center", new CenterCommand(npcRegistry))
 | 
				
			||||||
| 
						 | 
					@ -306,7 +320,8 @@ public class ZNpcsPlus {
 | 
				
			||||||
                .addSubcommand("storage", new MultiCommand(bootstrap.loadHelpMessage("storage"))
 | 
					                .addSubcommand("storage", new MultiCommand(bootstrap.loadHelpMessage("storage"))
 | 
				
			||||||
                        .addSubcommand("save", new SaveAllCommand(npcRegistry))
 | 
					                        .addSubcommand("save", new SaveAllCommand(npcRegistry))
 | 
				
			||||||
                        .addSubcommand("reload", new LoadAllCommand(npcRegistry))
 | 
					                        .addSubcommand("reload", new LoadAllCommand(npcRegistry))
 | 
				
			||||||
                        .addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry)))
 | 
					                        .addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
 | 
				
			||||||
 | 
					                        .addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
 | 
				
			||||||
                .addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
 | 
					                .addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
 | 
				
			||||||
                        .addSubcommand("add", new HoloAddCommand(npcRegistry))
 | 
					                        .addSubcommand("add", new HoloAddCommand(npcRegistry))
 | 
				
			||||||
                        .addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
 | 
					                        .addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,176 @@
 | 
				
			||||||
 | 
					package lol.pyr.znpcsplus.commands.storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lol.pyr.director.adventure.command.CommandContext;
 | 
				
			||||||
 | 
					import lol.pyr.director.adventure.command.CommandHandler;
 | 
				
			||||||
 | 
					import lol.pyr.director.common.command.CommandExecutionException;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.ZNpcsPlus;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.config.ConfigManager;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.storage.NpcStorage;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.storage.NpcStorageType;
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.Component;
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.format.NamedTextColor;
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicReference;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class MigrateCommand implements CommandHandler {
 | 
				
			||||||
 | 
					    private final ConfigManager configManager;
 | 
				
			||||||
 | 
					    private final ZNpcsPlus plugin;
 | 
				
			||||||
 | 
					    private final PacketFactory packetFactory;
 | 
				
			||||||
 | 
					    private final ActionRegistryImpl actionRegistry;
 | 
				
			||||||
 | 
					    private final NpcTypeRegistryImpl typeRegistry;
 | 
				
			||||||
 | 
					    private final EntityPropertyRegistryImpl propertyRegistry;
 | 
				
			||||||
 | 
					    private final LegacyComponentSerializer textSerializer;
 | 
				
			||||||
 | 
					    private final NpcStorage currentStorage;
 | 
				
			||||||
 | 
					    private final NpcStorageType currentStorageType;
 | 
				
			||||||
 | 
					    private final NpcRegistryImpl npcRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
 | 
				
			||||||
 | 
					        this.configManager = configManager;
 | 
				
			||||||
 | 
					        this.plugin = plugin;
 | 
				
			||||||
 | 
					        this.packetFactory = packetFactory;
 | 
				
			||||||
 | 
					        this.actionRegistry = actionRegistry;
 | 
				
			||||||
 | 
					        this.typeRegistry = typeRegistry;
 | 
				
			||||||
 | 
					        this.propertyRegistry = propertyRegistry;
 | 
				
			||||||
 | 
					        this.textSerializer = textSerializer;
 | 
				
			||||||
 | 
					        this.currentStorage = currentStorage;
 | 
				
			||||||
 | 
					        this.currentStorageType = currentStorageType;
 | 
				
			||||||
 | 
					        this.npcRegistry = npcRegistry;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void run(CommandContext context) throws CommandExecutionException {
 | 
				
			||||||
 | 
					        context.setUsage(context.getLabel() + " storage migrate <from> <to> [force]");
 | 
				
			||||||
 | 
					        NpcStorageType from = context.parse(NpcStorageType.class);
 | 
				
			||||||
 | 
					        NpcStorageType to = context.parse(NpcStorageType.class);
 | 
				
			||||||
 | 
					        boolean force = context.argSize() > 2 && context.parse(Boolean.class);
 | 
				
			||||||
 | 
					        if (from.equals(to)) {
 | 
				
			||||||
 | 
					            context.halt(Component.text("The storage types must be different.", NamedTextColor.RED));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        NpcStorage fromStorage;
 | 
				
			||||||
 | 
					        if (currentStorageType == from) {
 | 
				
			||||||
 | 
					            fromStorage = currentStorage;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
				
			||||||
 | 
					            if (fromStorage == null) {
 | 
				
			||||||
 | 
					                context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Collection<NpcEntryImpl> entries;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            entries = fromStorage.loadNpcs();
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            context.halt(Component.text("Failed to load NPCs from the source storage.", NamedTextColor.RED));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (entries.isEmpty()) {
 | 
				
			||||||
 | 
					            context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        NpcStorage toStorage;
 | 
				
			||||||
 | 
					        if (currentStorageType == to) {
 | 
				
			||||||
 | 
					            toStorage = currentStorage;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
				
			||||||
 | 
					            if (toStorage == null) {
 | 
				
			||||||
 | 
					                context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Collection<NpcEntryImpl> existingEntries;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            existingEntries = toStorage.loadNpcs();
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            context.halt(Component.text("Failed to load NPCs from the destination storage.", NamedTextColor.RED));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (existingEntries.isEmpty()) {
 | 
				
			||||||
 | 
					            toStorage.saveNpcs(entries);
 | 
				
			||||||
 | 
					            context.send(Component.text("Migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
 | 
				
			||||||
 | 
					                    .append(Component.text(from.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                    .append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
 | 
				
			||||||
 | 
					                    .append(Component.text(to.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                    .append(Component.text(").", NamedTextColor.GREEN)));
 | 
				
			||||||
 | 
					            if (currentStorageType == to) {
 | 
				
			||||||
 | 
					                npcRegistry.reload();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                toStorage.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!force) {
 | 
				
			||||||
 | 
					            Collection<NpcEntryImpl> toSave = entries.stream().filter(e -> existingEntries.stream().noneMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
 | 
				
			||||||
 | 
					            Collection<NpcEntryImpl> idExists = entries.stream().filter(e -> existingEntries.stream().anyMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
 | 
				
			||||||
 | 
					            if (toSave.isEmpty()) {
 | 
				
			||||||
 | 
					                context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
 | 
				
			||||||
 | 
					                if (currentStorageType != to) {
 | 
				
			||||||
 | 
					                    toStorage.close();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                toStorage.saveNpcs(toSave);
 | 
				
			||||||
 | 
					                context.send(Component.text("Migrated " + toSave.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
 | 
				
			||||||
 | 
					                        .append(Component.text(from.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                        .append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
 | 
				
			||||||
 | 
					                        .append(Component.text(to.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                        .append(Component.text(").", NamedTextColor.GREEN)));
 | 
				
			||||||
 | 
					                if (currentStorageType == to) {
 | 
				
			||||||
 | 
					                    npcRegistry.reload();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    toStorage.close();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!idExists.isEmpty()) {
 | 
				
			||||||
 | 
					                AtomicReference<Component> component = new AtomicReference<>(Component.text("The following NPCs were not migrated because their IDs already exist in the destination storage:").color(NamedTextColor.YELLOW));
 | 
				
			||||||
 | 
					                idExists.forEach(e -> {
 | 
				
			||||||
 | 
					                    component.set(component.get().append(Component.newline()).append(Component.text(e.getId(), NamedTextColor.RED)));
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                component.set(component.get().append(Component.newline())
 | 
				
			||||||
 | 
					                        .append(Component.text("Use the ", NamedTextColor.YELLOW))
 | 
				
			||||||
 | 
					                        .append(Component.text("force", NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                        .append(Component.text(" argument to overwrite them.", NamedTextColor.YELLOW)));
 | 
				
			||||||
 | 
					                context.send(component.get());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            toStorage.saveNpcs(entries);
 | 
				
			||||||
 | 
					            context.send(Component.text("Force migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
 | 
				
			||||||
 | 
					                    .append(Component.text(from.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                    .append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
 | 
				
			||||||
 | 
					                    .append(Component.text(to.name(), NamedTextColor.GOLD))
 | 
				
			||||||
 | 
					                    .append(Component.text(").", NamedTextColor.GREEN)));
 | 
				
			||||||
 | 
					            if (currentStorageType == to) {
 | 
				
			||||||
 | 
					                npcRegistry.reload();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                toStorage.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public List<String> suggest(CommandContext context) throws CommandExecutionException {
 | 
				
			||||||
 | 
					        if (context.argSize() == 1) {
 | 
				
			||||||
 | 
					            return context.suggestEnum(NpcStorageType.values());
 | 
				
			||||||
 | 
					        } else if (context.argSize() == 2) {
 | 
				
			||||||
 | 
					            NpcStorageType from = context.suggestionParse(0, NpcStorageType.class);
 | 
				
			||||||
 | 
					            if (from == null) return Collections.emptyList();
 | 
				
			||||||
 | 
					            return context.suggestCollection(Arrays.stream(NpcStorageType.values())
 | 
				
			||||||
 | 
					                    .filter(t -> t != from).map(Enum::name).collect(Collectors.toList()));
 | 
				
			||||||
 | 
					        } else if (context.argSize() == 3) {
 | 
				
			||||||
 | 
					            return context.suggestLiteral("true");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Collections.emptyList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -30,9 +30,14 @@ public interface DatabaseConfig {
 | 
				
			||||||
    @DefaultString("znpcsplus")
 | 
					    @DefaultString("znpcsplus")
 | 
				
			||||||
    String databaseName();
 | 
					    String databaseName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ConfKey("use-ssl")
 | 
				
			||||||
 | 
					    @ConfComments("Should SSL be used when connecting to the database?")
 | 
				
			||||||
 | 
					    @DefaultBoolean(false)
 | 
				
			||||||
 | 
					    boolean useSSL();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    default String createConnectionURL(String dbType) {
 | 
					    default String createConnectionURL(String dbType) {
 | 
				
			||||||
        if (dbType.equalsIgnoreCase("mysql")) {
 | 
					        if (dbType.equalsIgnoreCase("mysql")) {
 | 
				
			||||||
            return "jdbc:mysql://" + host() + ":" + port() + "/" + databaseName() + "?useSSL=false&user=" + username() + "&password=" + password();
 | 
					            return "jdbc:mysql://" + host() + ":" + port() + "/" + databaseName() + "?useSSL=" + useSSL();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            throw new IllegalArgumentException("Unsupported database type: " + dbType);
 | 
					            throw new IllegalArgumentException("Unsupported database type: " + dbType);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
 | 
				
			||||||
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
 | 
					import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
 | 
				
			||||||
import lol.pyr.znpcsplus.entity.serializers.*;
 | 
					import lol.pyr.znpcsplus.entity.serializers.*;
 | 
				
			||||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
					import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
				
			||||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
					import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
				
			||||||
import lol.pyr.znpcsplus.util.*;
 | 
					import lol.pyr.znpcsplus.util.*;
 | 
				
			||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
					import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
				
			||||||
| 
						 | 
					@ -104,7 +105,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer) {
 | 
					    public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) {
 | 
				
			||||||
        ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
 | 
					        ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
 | 
				
			||||||
        boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
 | 
					        boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
 | 
				
			||||||
        boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
 | 
					        boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
 | 
				
			||||||
| 
						 | 
					@ -127,7 +128,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(new DummyProperty<>("permission_required", false));
 | 
					        register(new DummyProperty<>("permission_required", false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(new ForceBodyRotationProperty(plugin));
 | 
					        register(new ForceBodyRotationProperty(plugin, taskScheduler));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(new DummyProperty<>("player_knockback", false));
 | 
					        register(new DummyProperty<>("player_knockback", false));
 | 
				
			||||||
        register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
 | 
					        register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
 | 
				
			||||||
| 
						 | 
					@ -659,6 +660,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Wolf
 | 
					        // Wolf
 | 
				
			||||||
        register(new EncodedIntegerProperty<>("wolf_variant", WoldVariant.PALE, wolfIndex, WoldVariant::getId, EntityDataTypes.WOLF_VARIANT));
 | 
					        register(new EncodedIntegerProperty<>("wolf_variant", WoldVariant.PALE, wolfIndex, WoldVariant::getId, EntityDataTypes.WOLF_VARIANT));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bogged
 | 
				
			||||||
 | 
					        register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void registerSerializer(PropertySerializer<?> serializer) {
 | 
					    private void registerSerializer(PropertySerializer<?> serializer) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,22 +3,26 @@ package lol.pyr.znpcsplus.entity.properties;
 | 
				
			||||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
					import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
				
			||||||
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
 | 
					import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
 | 
				
			||||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
					import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
				
			||||||
import org.bukkit.Bukkit;
 | 
					import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
				
			||||||
import org.bukkit.entity.Player;
 | 
					import org.bukkit.entity.Player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ForceBodyRotationProperty extends DummyProperty<Boolean> {
 | 
					public class ForceBodyRotationProperty extends DummyProperty<Boolean> {
 | 
				
			||||||
    private final ZNpcsPlusBootstrap plugin;
 | 
					    private final ZNpcsPlusBootstrap plugin;
 | 
				
			||||||
 | 
					    private final TaskScheduler scheduler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin) {
 | 
					    public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin, TaskScheduler scheduler) {
 | 
				
			||||||
        super("force_body_rotation", false);
 | 
					        super("force_body_rotation", false);
 | 
				
			||||||
        this.plugin = plugin;
 | 
					        this.plugin = plugin;
 | 
				
			||||||
 | 
					        this.scheduler = scheduler;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
					    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
				
			||||||
        Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 2L);
 | 
					        if (entity.getProperty(this)) {
 | 
				
			||||||
        Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 6L);
 | 
					            scheduler.runLaterAsync(() -> entity.swingHand(player, false), 2L);
 | 
				
			||||||
 | 
					            scheduler.runLaterAsync(() -> entity.swingHand(player, false), 6L);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ public class ConsoleCommandActionType implements InteractionActionType<ConsoleCo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void appendUsage(CommandContext context) {
 | 
					    public void appendUsage(CommandContext context) {
 | 
				
			||||||
        context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <server>");
 | 
					        context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <command>");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ public class PlayerChatActionType implements InteractionActionType<PlayerChatAct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void appendUsage(CommandContext context) {
 | 
					    public void appendUsage(CommandContext context) {
 | 
				
			||||||
        context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <server>");
 | 
					        context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <message>");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,6 +56,7 @@ public class NpcImpl extends Viewable implements Npc {
 | 
				
			||||||
        UNSAFE_hideAll();
 | 
					        UNSAFE_hideAll();
 | 
				
			||||||
        this.type = type;
 | 
					        this.type = type;
 | 
				
			||||||
        entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation());
 | 
					        entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation());
 | 
				
			||||||
 | 
					        hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
 | 
				
			||||||
        UNSAFE_showAll();
 | 
					        UNSAFE_showAll();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -231,10 +232,17 @@ public class NpcImpl extends Viewable implements Npc {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void setWorld(World world) {
 | 
					    public void setWorld(World world) {
 | 
				
			||||||
 | 
					        if (world == null) throw new IllegalArgumentException("world can not be null");
 | 
				
			||||||
        delete();
 | 
					        delete();
 | 
				
			||||||
        this.worldName = world.getName();
 | 
					        this.worldName = world.getName();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setWorld(String name) {
 | 
				
			||||||
 | 
					        if (name == null) throw new IllegalArgumentException("world name can not be null");
 | 
				
			||||||
 | 
					        delete();
 | 
				
			||||||
 | 
					        this.worldName = name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void swingHand(boolean offHand) {
 | 
					    public void swingHand(boolean offHand) {
 | 
				
			||||||
        for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
 | 
					        for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -207,5 +207,10 @@ public class NpcRegistryImpl implements NpcRegistry {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void unload() {
 | 
					    public void unload() {
 | 
				
			||||||
        npcList.forEach(npcEntry -> npcEntry.getNpc().delete());
 | 
					        npcList.forEach(npcEntry -> npcEntry.getNpc().delete());
 | 
				
			||||||
 | 
					        storage.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public NpcStorage getStorage() {
 | 
				
			||||||
 | 
					        return storage;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
        // Most hologram offsets generated using Entity#getHeight() in 1.19.4
 | 
					        // Most hologram offsets generated using Entity#getHeight() in 1.19.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "armor_stand", EntityTypes.ARMOR_STAND)
 | 
					        register(builder(p, "armor_stand", EntityTypes.ARMOR_STAND)
 | 
				
			||||||
                .setHologramOffset(-0.15)
 | 
					                .setHologramOffset(0)
 | 
				
			||||||
                .addEquipmentProperties()
 | 
					                .addEquipmentProperties()
 | 
				
			||||||
                .addProperties("small", "arms", "base_plate", "head_rotation", "body_rotation", "left_arm_rotation", "right_arm_rotation", "left_leg_rotation", "right_leg_rotation"));
 | 
					                .addProperties("small", "arms", "base_plate", "head_rotation", "body_rotation", "left_arm_rotation", "right_arm_rotation", "left_leg_rotation", "right_leg_rotation"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,7 +106,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
        register(builder(p, "iron_golem", EntityTypes.IRON_GOLEM)
 | 
					        register(builder(p, "iron_golem", EntityTypes.IRON_GOLEM)
 | 
				
			||||||
                .setHologramOffset(0.725));
 | 
					                .setHologramOffset(0.725));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "magma_cube", EntityTypes.MAGMA_CUBE)); // TODO: Hologram offset scaling with size property
 | 
					        register(builder(p, "magma_cube", EntityTypes.MAGMA_CUBE)
 | 
				
			||||||
 | 
					                .setHologramOffset(-1.455)); // TODO: Hologram offset scaling with size property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "mooshroom", EntityTypes.MOOSHROOM)
 | 
					        register(builder(p, "mooshroom", EntityTypes.MOOSHROOM)
 | 
				
			||||||
                .setHologramOffset(-0.575)
 | 
					                .setHologramOffset(-0.575)
 | 
				
			||||||
| 
						 | 
					@ -137,7 +138,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
        register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
 | 
					        register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
 | 
				
			||||||
                .setHologramOffset(-0.375));
 | 
					                .setHologramOffset(-0.375));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "slime", EntityTypes.SLIME)); // TODO: Hologram offset scaling with size property
 | 
					        register(builder(p, "slime", EntityTypes.SLIME)
 | 
				
			||||||
 | 
					                .setHologramOffset(-1.455)); // TODO: Hologram offset scaling with size property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "snow_golem", EntityTypes.SNOW_GOLEM)
 | 
					        register(builder(p, "snow_golem", EntityTypes.SNOW_GOLEM)
 | 
				
			||||||
                .setHologramOffset(-0.075)
 | 
					                .setHologramOffset(-0.075)
 | 
				
			||||||
| 
						 | 
					@ -230,7 +232,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
                .addEquipmentProperties());
 | 
					                .addEquipmentProperties());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
 | 
					        register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
 | 
				
			||||||
                .setHologramOffset(-1.0)
 | 
					                .setHologramOffset(-0.025)
 | 
				
			||||||
                .addEquipmentProperties());
 | 
					                .addEquipmentProperties());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
 | 
					        if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
 | 
				
			||||||
| 
						 | 
					@ -315,7 +317,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
                .addProperties("hoglin_immune_to_zombification"));
 | 
					                .addProperties("hoglin_immune_to_zombification"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "piglin", EntityTypes.PIGLIN)
 | 
					        register(builder(p, "piglin", EntityTypes.PIGLIN)
 | 
				
			||||||
                .setHologramOffset(-1.0)
 | 
					                .setHologramOffset(-0.025)
 | 
				
			||||||
                .addEquipmentProperties()
 | 
					                .addEquipmentProperties()
 | 
				
			||||||
                .addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing"));
 | 
					                .addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -363,19 +365,28 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
				
			||||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
 | 
					        if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "sniffer", EntityTypes.SNIFFER)
 | 
					        register(builder(p, "sniffer", EntityTypes.SNIFFER)
 | 
				
			||||||
                .setHologramOffset(0.125)
 | 
					                .setHologramOffset(0.075)
 | 
				
			||||||
                .addProperties("sniffer_state"));
 | 
					                .addProperties("sniffer_state"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "camel", EntityTypes.CAMEL)
 | 
					        register(builder(p, "camel", EntityTypes.CAMEL)
 | 
				
			||||||
                .setHologramOffset(0.25)
 | 
					                .setHologramOffset(0.4)
 | 
				
			||||||
                .addProperties("bashing", "camel_sitting"));
 | 
					                .addProperties("bashing", "camel_sitting"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
 | 
					        if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        register(builder(p, "armadillo", EntityTypes.ARMADILLO)
 | 
					        register(builder(p, "armadillo", EntityTypes.ARMADILLO)
 | 
				
			||||||
                .setHologramOffset(-1.475)
 | 
					                .setHologramOffset(-1.325)
 | 
				
			||||||
                .addProperties("armadillo_state"));
 | 
					                .addProperties("armadillo_state"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!version.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        register(builder(p, "bogged", EntityTypes.BOGGED)
 | 
				
			||||||
 | 
					                .setHologramOffset(0.015)
 | 
				
			||||||
 | 
					                .addProperties("bogged_sheared"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        register(builder(p, "breeze", EntityTypes.BREEZE)
 | 
				
			||||||
 | 
					                .setHologramOffset(-0.205));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Collection<NpcType> getAll() {
 | 
					    public Collection<NpcType> getAll() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					package lol.pyr.znpcsplus.packets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.PacketEventsAPI;
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.protocol.entity.EntityPositionData;
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.util.Vector3d;
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityHeadLook;
 | 
				
			||||||
 | 
					import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityTeleport;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.config.ConfigManager;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
				
			||||||
 | 
					import lol.pyr.znpcsplus.util.NpcLocation;
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
				
			||||||
 | 
					import org.bukkit.entity.Player;
 | 
				
			||||||
 | 
					import org.bukkit.plugin.Plugin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class V1_21_3PacketFactory extends V1_20_2PacketFactory {
 | 
				
			||||||
 | 
					    public V1_21_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
 | 
				
			||||||
 | 
					        super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void teleportEntity(Player player, PacketEntity entity) {
 | 
				
			||||||
 | 
					        NpcLocation location = entity.getLocation();
 | 
				
			||||||
 | 
					        sendPacket(player, new WrapperPlayServerEntityTeleport(entity.getEntityId(), new EntityPositionData(npcLocationToVector(location), new Vector3d(0, 0, 0), location.getYaw(), location.getPitch()), RelativeFlag.NONE, false));
 | 
				
			||||||
 | 
					        sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
 | 
				
			||||||
import org.bukkit.entity.Player;
 | 
					import org.bukkit.entity.Player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +22,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
 | 
				
			||||||
    static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
 | 
					    static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
 | 
				
			||||||
        String[] arr = str.split(";");
 | 
					        String[] arr = str.split(";");
 | 
				
			||||||
        if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
 | 
					        if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
 | 
				
			||||||
        else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, arr[1]);
 | 
					        else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, String.join(";", Arrays.copyOfRange(arr, 1, arr.length)));
 | 
				
			||||||
        else if (arr[0].equalsIgnoreCase("prefetched")) {
 | 
					        else if (arr[0].equalsIgnoreCase("prefetched")) {
 | 
				
			||||||
            List<TextureProperty> properties = new ArrayList<>();
 | 
					            List<TextureProperty> properties = new ArrayList<>();
 | 
				
			||||||
            for (int i = 0; i < (arr.length - 1) / 3; i++) {
 | 
					            for (int i = 0; i < (arr.length - 1) / 3; i++) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,4 +8,7 @@ public interface NpcStorage {
 | 
				
			||||||
    Collection<NpcEntryImpl> loadNpcs();
 | 
					    Collection<NpcEntryImpl> loadNpcs();
 | 
				
			||||||
    void saveNpcs(Collection<NpcEntryImpl> npcs);
 | 
					    void saveNpcs(Collection<NpcEntryImpl> npcs);
 | 
				
			||||||
    void deleteNpc(NpcEntryImpl npc);
 | 
					    void deleteNpc(NpcEntryImpl npc);
 | 
				
			||||||
 | 
					    default void close() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,4 +13,6 @@ public abstract class Database {
 | 
				
			||||||
    public abstract Connection getSQLConnection();
 | 
					    public abstract Connection getSQLConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public abstract void load();
 | 
					    public abstract void load();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public abstract void close();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,10 +10,14 @@ import java.util.logging.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class MySQL extends Database {
 | 
					public class MySQL extends Database {
 | 
				
			||||||
    private final String connectionURL;
 | 
					    private final String connectionURL;
 | 
				
			||||||
 | 
					    private final String username;
 | 
				
			||||||
 | 
					    private final String password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MySQL(String connectionURL, Logger logger) {
 | 
					    public MySQL(String connectionURL, String username, String password, Logger logger) {
 | 
				
			||||||
        super(logger);
 | 
					        super(logger);
 | 
				
			||||||
        this.connectionURL = connectionURL;
 | 
					        this.connectionURL = connectionURL;
 | 
				
			||||||
 | 
					        this.username = username;
 | 
				
			||||||
 | 
					        this.password = password;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
| 
						 | 
					@ -25,7 +29,7 @@ public class MySQL extends Database {
 | 
				
			||||||
                return connection;
 | 
					                return connection;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Class.forName("com.mysql.jdbc.Driver");
 | 
					            Class.forName("com.mysql.jdbc.Driver");
 | 
				
			||||||
            connection = java.sql.DriverManager.getConnection(connectionURL);
 | 
					            connection = java.sql.DriverManager.getConnection(connectionURL, username, password);
 | 
				
			||||||
            return connection;
 | 
					            return connection;
 | 
				
			||||||
        } catch (ClassNotFoundException ex) {
 | 
					        } catch (ClassNotFoundException ex) {
 | 
				
			||||||
            logger.severe("MySQL JDBC library not found" + ex);
 | 
					            logger.severe("MySQL JDBC library not found" + ex);
 | 
				
			||||||
| 
						 | 
					@ -56,6 +60,18 @@ public class MySQL extends Database {
 | 
				
			||||||
        connection = getSQLConnection();
 | 
					        connection = getSQLConnection();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void close() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (connection != null) {
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (SQLException e) {
 | 
				
			||||||
 | 
					            logger.severe("An error occurred while closing the connection");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean tableExists(String tableName) {
 | 
					    public boolean tableExists(String tableName) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            Statement s = connection.createStatement();
 | 
					            Statement s = connection.createStatement();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,8 @@ public class MySQLStorage implements NpcStorage {
 | 
				
			||||||
        this.typeRegistry = typeRegistry;
 | 
					        this.typeRegistry = typeRegistry;
 | 
				
			||||||
        this.propertyRegistry = propertyRegistry;
 | 
					        this.propertyRegistry = propertyRegistry;
 | 
				
			||||||
        this.textSerializer = textSerializer;
 | 
					        this.textSerializer = textSerializer;
 | 
				
			||||||
        this.database = new MySQL(configManager.getConfig().databaseConfig().createConnectionURL("mysql"), logger);
 | 
					        this.database = new MySQL(configManager.getConfig().databaseConfig().createConnectionURL("mysql"),
 | 
				
			||||||
 | 
					                configManager.getConfig().databaseConfig().username(), configManager.getConfig().databaseConfig().password(), logger);
 | 
				
			||||||
        database.load();
 | 
					        database.load();
 | 
				
			||||||
        if (database.getSQLConnection() == null) {
 | 
					        if (database.getSQLConnection() == null) {
 | 
				
			||||||
            throw new RuntimeException("Failed to initialize MySQL Storage");
 | 
					            throw new RuntimeException("Failed to initialize MySQL Storage");
 | 
				
			||||||
| 
						 | 
					@ -313,4 +314,9 @@ public class MySQLStorage implements NpcStorage {
 | 
				
			||||||
            exception.printStackTrace();
 | 
					            exception.printStackTrace();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void close() {
 | 
				
			||||||
 | 
					        database.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,18 @@ public class SQLite extends Database{
 | 
				
			||||||
        connection = getSQLConnection();
 | 
					        connection = getSQLConnection();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void close() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (connection != null) {
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (SQLException e) {
 | 
				
			||||||
 | 
					            logger.severe("An error occurred while closing the connection");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean tableExists(String tableName) {
 | 
					    public boolean tableExists(String tableName) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            Statement s = connection.createStatement();
 | 
					            Statement s = connection.createStatement();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -312,4 +312,9 @@ public class SQLiteStorage implements NpcStorage {
 | 
				
			||||||
            exception.printStackTrace();
 | 
					            exception.printStackTrace();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void close() {
 | 
				
			||||||
 | 
					        database.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,21 @@
 | 
				
			||||||
package lol.pyr.znpcsplus.updater;
 | 
					package lol.pyr.znpcsplus.updater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import me.robertlit.spigotresources.api.Resource;
 | 
					import com.google.gson.JsonObject;
 | 
				
			||||||
import me.robertlit.spigotresources.api.SpigotResourcesAPI;
 | 
					import com.google.gson.JsonParser;
 | 
				
			||||||
import org.bukkit.plugin.PluginDescriptionFile;
 | 
					import org.bukkit.plugin.PluginDescriptionFile;
 | 
				
			||||||
import org.bukkit.scheduler.BukkitRunnable;
 | 
					import org.bukkit.scheduler.BukkitRunnable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.InputStreamReader;
 | 
				
			||||||
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class UpdateChecker extends BukkitRunnable {
 | 
					public class UpdateChecker extends BukkitRunnable {
 | 
				
			||||||
    private final static Logger logger = Logger.getLogger("ZNPCsPlus Update Checker");
 | 
					    private final static Logger logger = Logger.getLogger("ZNPCsPlus Update Checker");
 | 
				
			||||||
    private final static int RESOURCE_ID = 109380;
 | 
					    private final static String GET_RESOURCE = "https://api.spigotmc.org/simple/0.2/index.php?action=getResource&id=109380";
 | 
				
			||||||
    public final static String DOWNLOAD_LINK = "https://www.spigotmc.org/resources/znpcsplus.109380/";
 | 
					    public final static String DOWNLOAD_LINK = "https://www.spigotmc.org/resources/znpcsplus.109380/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final SpigotResourcesAPI api = new SpigotResourcesAPI(1, TimeUnit.MINUTES);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final PluginDescriptionFile info;
 | 
					    private final PluginDescriptionFile info;
 | 
				
			||||||
    private Status status = Status.UNKNOWN;
 | 
					    private Status status = Status.UNKNOWN;
 | 
				
			||||||
    private String newestVersion = "N/A";
 | 
					    private String newestVersion = "N/A";
 | 
				
			||||||
| 
						 | 
					@ -24,9 +25,29 @@ public class UpdateChecker extends BukkitRunnable {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void run() {
 | 
					    public void run() {
 | 
				
			||||||
        Resource resource = api.getResource(RESOURCE_ID).join();
 | 
					        String foundVersion = null;
 | 
				
			||||||
        if (resource == null) return;
 | 
					        try {
 | 
				
			||||||
        newestVersion = resource.getVersion();
 | 
					            URL getResource = new URL(GET_RESOURCE);
 | 
				
			||||||
 | 
					            HttpURLConnection httpRequest = ((HttpURLConnection) getResource.openConnection());
 | 
				
			||||||
 | 
					            httpRequest.setRequestMethod("GET");
 | 
				
			||||||
 | 
					            httpRequest.setConnectTimeout(5_000);
 | 
				
			||||||
 | 
					            httpRequest.setReadTimeout(5_000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (httpRequest.getResponseCode() == HttpURLConnection.HTTP_OK) {
 | 
				
			||||||
 | 
					                try (InputStreamReader reader = new InputStreamReader(httpRequest.getInputStream())) {
 | 
				
			||||||
 | 
					                    JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject();
 | 
				
			||||||
 | 
					                    foundVersion = jsonObject.get("current_version").getAsString();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                logger.warning("Failed to check for updates: HTTP response code " + httpRequest.getResponseCode());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            logger.warning("Failed to check for updates: " + e.getMessage());
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (foundVersion == null) return;
 | 
				
			||||||
 | 
					        newestVersion = foundVersion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        status = compareVersions(info.getVersion(), newestVersion);
 | 
					        status = compareVersions(info.getVersion(), newestVersion);
 | 
				
			||||||
        if (status == Status.UPDATE_NEEDED) notifyConsole();
 | 
					        if (status == Status.UPDATE_NEEDED) notifyConsole();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,12 +3,12 @@ package lol.pyr.znpcsplus.user;
 | 
				
			||||||
import org.bukkit.Bukkit;
 | 
					import org.bukkit.Bukkit;
 | 
				
			||||||
import org.bukkit.entity.Player;
 | 
					import org.bukkit.entity.Player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class UserManager {
 | 
					public class UserManager {
 | 
				
			||||||
    private final Map<UUID, User> userMap = new HashMap<>();
 | 
					    private final Map<UUID, User> userMap = new ConcurrentHashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public UserManager() {
 | 
					    public UserManager() {
 | 
				
			||||||
        Bukkit.getOnlinePlayers().forEach(this::get);
 | 
					        Bukkit.getOnlinePlayers().forEach(this::get);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					<gray>Examples:
 | 
				
			||||||
 | 
					 <gold>* <yellow>/npc action add <gold>consolecommand cool_npc1 ANY_CLICK 0 0 say {player} just clicked a cool npc!
 | 
				
			||||||
 | 
					 <gold>* <yellow>/npc action add <gold>playerchat dog LEFT_CLICK 0 100 It has been 5 seconds since i clicked the npc
 | 
				
			||||||
 | 
					 <gold>* <yellow>/npc action add <gold>message npc123 RIGHT_CLICK 1 0 You can only click this npc once per second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Action Types:
 | 
				
			||||||
 | 
					 <gold>* <yellow>Console Command <gray>- Send a console command when a player interacts with the npc
 | 
				
			||||||
 | 
					 <gold>* <yellow>Message <gray>- Send a message to any player that interacts with the npc
 | 
				
			||||||
 | 
					 <gold>* <yellow>Player Chat <gray>- Make any player that interacts send something in the chat
 | 
				
			||||||
 | 
					 <gold>* <yellow>Player Command <gray>- Make any player that interacts send a command
 | 
				
			||||||
 | 
					 <gold>* <yellow>Switch Server <gray>- Send the player to a different server on the proxy using bungee messaging channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to add actions to an npc
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<gray>Usage <gold>» <yellow>/npc action clear <gold><id>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to clear all npc actions
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<gray>Usage <gold>» <yellow>/npc action delete <gold><id> <action id>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to delete a specific action from an npc
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<gray>Usage <gold>» <yellow>/npc action edit <gold><id> <action id> <type> <args>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to change a specific action on an npc
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					<gray>Usage <gold>» <yellow>/npc action list <gold><id>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to list all actions of an npc
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,16 @@
 | 
				
			||||||
<gray>Hover over any command more info
 | 
					<gray>Hover over any command more info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 <hover:show_text:'{@holo-hover/add}'><gold>* <yellow>/npc holo add <id> <text></hover>
 | 
					 <hover:show_text:'{@holo-hover/add}'><gold>* <yellow>/npc holo add <id> <text></hover>
 | 
				
			||||||
 <hover:show_text:'{@holo-hover/delete}'><gold>* <yellow>/npc holo delete <id> <line></hover>
 | 
					 | 
				
			||||||
 <hover:show_text:'{@holo-hover/set}'><gold>* <yellow>/npc holo set <id> <line> <text></hover>
 | 
					 <hover:show_text:'{@holo-hover/set}'><gold>* <yellow>/npc holo set <id> <line> <text></hover>
 | 
				
			||||||
 <hover:show_text:'{@holo-hover/insert}'><gold>* <yellow>/npc holo insert <id> <line> <text></hover>
 | 
					 <hover:show_text:'{@holo-hover/insert}'><gold>* <yellow>/npc holo insert <id> <line> <text></hover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 <hover:show_text:'{@holo-hover/additem}'><gold>* <yellow>/npc holo additem <id></hover>
 | 
				
			||||||
 | 
					 <hover:show_text:'{@holo-hover/setitem}'><gold>* <yellow>/npc holo setitem <id> <line></hover>
 | 
				
			||||||
 | 
					 <hover:show_text:'{@holo-hover/insertitem}'><gold>* <yellow>/npc holo insertitem <id> <line></hover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 <hover:show_text:'{@holo-hover/delete}'><gold>* <yellow>/npc holo delete <id> <line></hover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 <hover:show_text:'{@holo-hover/offset}'><gold>* <yellow>/npc holo offset <id> <amount></hover>
 | 
					 <hover:show_text:'{@holo-hover/offset}'><gold>* <yellow>/npc holo offset <id> <amount></hover>
 | 
				
			||||||
 | 
					 <hover:show_text:'{@holo-hover/refreshdelay}'><gold>* <yellow>/npc holo refreshdelay <id> <delay></hover>
 | 
				
			||||||
 <hover:show_text:'{@holo-hover/info}'><gold>* <yellow>/npc holo info <id></hover>
 | 
					 <hover:show_text:'{@holo-hover/info}'><gold>* <yellow>/npc holo info <id></hover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								plugin/src/main/resources/messages/storage-hover/migrate.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								plugin/src/main/resources/messages/storage-hover/migrate.txt
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					<gray>Usage <gold>» <yellow>/npc storage migrate <gold><from> <to> [force]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Storage Types:
 | 
				
			||||||
 | 
					 <gold>* <yellow>YAML <gray>- Npcs are stored in yaml files
 | 
				
			||||||
 | 
					 <gold>* <yellow>SQLite <gray>- Npcs are stored in a SQLite database
 | 
				
			||||||
 | 
					 <gold>* <yellow>MySQL <gray>- Npcs are stored in a MySQL database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>Command used to migrate npcs from one storage type to another.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This command will NOT delete the original storage files or database,
 | 
				
			||||||
 | 
					but will copy the npcs to the new storage type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<gray>This will also not overwrite any existing npcs in the new storage
 | 
				
			||||||
 | 
					type, unless the <gold>force <gray>argument is set to <gold>true<gray>.
 | 
				
			||||||
 | 
					<red>Warning: <bold>force</bold> will overwrite any existing npcs with the same id
 | 
				
			||||||
 | 
					in the new storage type and CANNOT be undone.
 | 
				
			||||||
| 
						 | 
					@ -5,4 +5,5 @@
 | 
				
			||||||
 <hover:show_text:'{@storage-hover/save}'><gold>* <yellow>/npc storage save</hover>
 | 
					 <hover:show_text:'{@storage-hover/save}'><gold>* <yellow>/npc storage save</hover>
 | 
				
			||||||
 <hover:show_text:'{@storage-hover/reload}'><gold>* <yellow>/npc storage reload</hover>
 | 
					 <hover:show_text:'{@storage-hover/reload}'><gold>* <yellow>/npc storage reload</hover>
 | 
				
			||||||
 <hover:show_text:'{@storage-hover/import}'><gold>* <yellow>/npc storage import <importer></hover>
 | 
					 <hover:show_text:'{@storage-hover/import}'><gold>* <yellow>/npc storage import <importer></hover>
 | 
				
			||||||
 | 
					 <hover:show_text:'{@storage-hover/migrate}'><gold>* <yellow>/npc storage migrate <from> <to> [force]</hover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue